I am trying to use Boost ProgramOptions to parse a config file to initialize my own class type Dataset (code below)
I am adding the option as:
config_.add_options()("dataset", po::value<Dataset>()->required(),"Dataset");
My Dataset class is defined as following:
#ifndef DATASET_H_
#define DATASET_H_
#include <boost/algorithm/string/predicate.hpp>
class Dataset {
public:
enum Name {
JAVA, SUMATRA, ASIA, AFRICA
};
Dataset(Name name = JAVA) : name_(name) {}
operator Name () const {return name_;}
std::string asString() const;
private:
Name name_;
//prevent automatic conversion for any other built-in types such as bool, int, etc
template<typename T>
operator T() const;
};
inline std::string Dataset::asString() const {
if (name_ == Dataset::JAVA)
return "JAVA";
else if (name_ == Dataset::SUMATRA)
return "SUMATRA";
else if (name_ == Dataset::ASIA)
return "ASIA";
else if (name_ == Dataset::AFRICA)
return "AFRICA";
else
return "UNKNOWN DATASET";
}
inline std::ostream& operator<<(std::ostream& os, const Dataset& dataset) {
os << dataset.asString();
return os;
}
inline std::istream& operator>>(std::istream& in, Dataset& dataset) {
std::string token;
in >> token;
if (boost::iequals(token, "java"))
dataset = Dataset::JAVA;
else if (boost::iequals(token, "sumatra"))
dataset = Dataset::SUMATRA;
else if (boost::iequals(token, "asia"))
dataset = Dataset::ASIA;
else if (boost::iequals(token, "africa"))
dataset = Dataset::AFRICA;
else
throw std::runtime_error("Invalid Dataset Name");
return in;
}
#endif // DATASET_H_
I thought I have done everything, atleast this is what required to make boost happy for my other custom types. But I am getting this compile error, which I cannot comprehend:
In file included from /usr/include/boost/type_traits/has_right_shift.hpp:43:0,
from /usr/include/boost/lexical_cast.hpp:171,
from /usr/include/boost/program_options/value_semantic.hpp:14,
from /usr/include/boost/program_options/options_description.hpp:13,
from /usr/include/boost/program_options.hpp:15,
~/ProgramOptions.cpp:1:
/usr/include/boost/type_traits/detail/has_binary_operator.hpp: In instantiation of ‘const bool boost::detail::has_right_shift_impl::operator_exists<std::basic_istream<wchar_t>, Dataset>::value’:
/usr/include/boost/type_traits/detail/has_binary_operator.hpp:179:4: instantiated from ‘const bool boost::detail::has_right_shift_impl::trait_impl1<std::basic_istream<wchar_t>, Dataset, boost::detail::has_right_shift_impl::dont_care, false>::value’
/usr/include/boost/type_traits/detail/has_binary_operator.hpp:214:4: instantiated from ‘const bool boost::detail::has_right_shift_impl::trait_impl<std::basic_istream<wchar_t>, Dataset, boost::detail::has_right_shift_impl::dont_care>::value’
/usr/include/boost/type_traits/detail/has_binary_operator.hpp:221:1: instantiated from ‘boost::has_right_shift<std::basic_istream<wchar_t>, Dataset, boost::detail::has_right_shift_impl::dont_care>’
/usr/include/boost/lexical_cast.hpp:394:1: instantiated from ‘boost::detail::deduce_target_char_impl<boost::detail::deduce_character_type_later<Dataset> >’
/usr/include/boost/lexical_cast.hpp:420:89: instantiated from ‘boost::detail::deduce_target_char<Dataset>’
/usr/include/boost/lexical_cast.hpp:679:92: instantiated from ‘boost::detail::lexical_cast_stream_traits<std::basic_string<char>, Dataset>’
/usr/include/boost/lexical_cast.hpp:2339:19: instantiated from ‘static Target boost::detail::lexical_cast_do_cast<Target, Source>::lexical_cast_impl(const Source&) [with Target = Dataset, Source = std::basic_string<char>]’
/usr/include/boost/lexical_cast.hpp:2519:50: instantiated from ‘Target boost::lexical_cast(const Source&) [with Target = Dataset, Source = std::basic_string<char>]’
/usr/include/boost/program_options/detail/value_semantic.hpp:89:13: instantiated from ‘void boost::program_options::validate(boost::any&, const std::vector<std::basic_string<charT> >&, T*, long int) [with T = Dataset, charT = char]’
/usr/include/boost/program_options/detail/value_semantic.hpp:170:13: instantiated from ‘void boost::program_options::typed_value<T, charT>::xparse(boost::any&, const std::vector<std::basic_string<charT> >&) const [with T = Dataset, charT = char]’
~/ProgramOptions.cpp:276:1: instantiated from here
/usr/include/boost/type_traits/detail/has_binary_operator.hpp:158:4: error: ambiguous overload for ‘operator>>’ in ‘boost::detail::has_right_shift_impl::make [with T = std::basic_istream<wchar_t>]() >> boost::detail::has_right_shift_impl::make [with T = Dataset]()’
/usr/include/boost/type_traits/detail/has_binary_operator.hpp:158:4: note: candidates are:
/usr/include/c++/4.6/istream:122:7: note: std::basic_istream<_CharT, _Traits>::__istream_type& std::basic_istream<_CharT, _Traits>::operator>>(std::basic_istream<_CharT, _Traits>::__istream_type& (*)(std::basic_istream<_CharT, _Traits>::__istream_type&)) [with _CharT = wchar_t, _Traits = std::char_traits<wchar_t>, std::basic_istream<_CharT, _Traits>::__istream_type = std::basic_istream<wchar_t>]
/usr/include/c++/4.6/istream:126:7: note: std::basic_istream<_CharT, _Traits>::__istream_type& std::basic_istream<_CharT, _Traits>::operator>>(std::basic_istream<_CharT, _Traits>::__ios_type& (*)(std::basic_istream<_CharT, _Traits>::__ios_type&)) [with _CharT = wchar_t, _Traits = std::char_traits<wchar_t>, std::basic_istream<_CharT, _Traits>::__istream_type = std::basic_istream<wchar_t>, std::basic_istream<_CharT, _Traits>::__ios_type = std::basic_ios<wchar_t>]
/usr/include/c++/4.6/istream:133:7: note: std::basic_istream<_CharT, _Traits>::__istream_type& std::basic_istream<_CharT, _Traits>::operator>>(std::ios_base& (*)(std::ios_base&)) [with _CharT = wchar_t, _Traits = std::char_traits<wchar_t>, std::basic_istream<_CharT, _Traits>::__istream_type = std::basic_istream<wchar_t>]
/usr/include/c++/4.6/istream:241:7: note: std::basic_istream<_CharT, _Traits>& std::basic_istream<_CharT, _Traits>::operator>>(std::basic_istream<_CharT, _Traits>::__streambuf_type*) [with _CharT = wchar_t, _Traits = std::char_traits<wchar_t>, std::basic_istream<_CharT, _Traits>::__streambuf_type = std::basic_streambuf<wchar_t>]
/usr/include/boost/type_traits/detail/has_binary_operator.hpp:70:13: note: boost::detail::has_right_shift_impl::no_operator boost::detail::has_right_shift_impl::operator>>(const boost::detail::has_right_shift_impl::any&, const boost::detail::has_right_shift_impl::any&)
Note: It does compile if I remove my type safety for my Dataset class, i.e remove the
template<typename T>
operator T() const;
Update: With excellent advice from @Arne Mertz, I modidied my Dataset class to be as following
class Dataset {
public:
enum Name {
JAVA, SUMATRA, ASIA, AFRICA
};
Dataset(const Name& name = JAVA) : name_(name) {}
Dataset(const Dataset& dataset) : name_(dataset.name_) {}
Dataset& operator=(const Dataset& dataset) {name_ = dataset.name_; return *this; }
bool operator==(const Dataset& dataset) const { return name_ == dataset.name_; }
bool operator!=(const Dataset& dataset) const { return name_ != dataset.name_; }
Name name() const {return name_;}
std::string asString() const;
private:
Name name_;
};
This I guess provides some of my desired type-safety like:
Dataset d(1); // Should be error
Dataset d = Dataset::JAVA; // Should be fine
double val = d; // Should be error
if(d == Dataset::SUMATRA) {} // should be fine
if(d == 3) {} // Should be error
The templated operator T()
is the reason, and the workings of how the compiler does lookup for the operator>>
:
Somewhere deep in the guts of boost, there is something like is >> some_dataset
. Since it is in the boost namespace, and not in yours, the compiler first looks if there is a proper operator inside the accessible boost namespaces or a member operator in the istream
class, that can be called without type conversions. Obviously there is not.
As a second approach, it looks if there are such operators accessible with a type conversion. Enters your "convert me to everything" operator and the different overloads of istream::operator>>
you see as "candidates" in the error message. The overloads accept different types of function pointers and streambuf pointers, and since your class is convertible to any of those, the compiler does not know whether it shall convert the Dataset to a streambuf*
or something else.
As a third option, i.e. when the operator T()
is not present, the compiler does argument dependent lookup (ADL), meaning it scans the namespaces of both arguments (i.e. namespace std
for the istream
and your namespace for the Dataset
) and finds the proper operator>>
in your namespace, so it works if you don't declare that conversion operator.
This is a perfect example of why allowing implicit conversions can be problematic and should be avoided - even more if it is an implicit conversion to anything like your operator T()
.
One solution could be to make the conversion operator explicit
(C++11 only), probably even better would be a named conversion function. An alternative could be restricting the types a Dataset
can be converted into vía std::enable_if
, or by explicitly writing each non-templated conversion operator if there are only a few target types.