
From: <Vladimir.Batov@wrsa.com.au>
From: "Stewart, Robert" <Robert.Stewart@sig.com> I was thinking a bit about syntax for the default value for the notional boost::convert() and kin. How about the following:
int const i(3); string const s("something"); int const j(convert<int>(s) | 5);
Yes, this idea is similar to
int j = convert<int>(s).or_default(5);
but does not introduce new vocabulary (my main hesitation). With the suggested op|, as I am reading it, it makes perfect reading just like I'd read English. And that is usually my criterion for an interface. So, I am voting 'yes'... in principle.
Now I am not so sure. In isolation it looks good:
int j = convert_to<int>(s) | 5;
IMHO, operator|() just has the wrong precedence for this kind of syntax as, most of the time, it needs to be put in parenthesis's.
What happens when we start mixing with
int j = convert_to<int>(s) | 5 >> std::hex; int j = convert_to<int>(s) | 5 >> boost::dothow >> std::hex; or even int j = convert_to<int>(s) >> boost::dothow >> std::hex | 5;
Some additional remarks. 1) Streams have proved to be horrible at formatting. Why stick with this scheme? Why not invent something more flexible like: int j = convert_to<hex>(s); ? 2) What about more complex conversions like: int i, j; convert(int_ >> ", " >> int_, "1,2", tie(i, j)); assert(i == 1 && j == 2); which brings us directly into the domain of Spirit (and, if you change convert for parse, you'll almost get a valid spirit expression, btw). So here is my question again. What's the point in duplicating Spirit's functionality? I understand that you strive for a simpler syntax for primitive conversion operations. So, let's think about how to put this simple API on top of Spirit, covering the primitive conversion, but having a well defined migration path towards more complex conversion needs. Converting a simple integer in Spirit is a bit verbose: int i; if (parse(int_, "1", i)) // ... succeeded But OTOH, this nicely integrates with a convert() function to be put on top of it: template <typename T> struct convert_to; template <> struct convert_to<int> { static int call(char const* p) { int i; if (spirit::qi::parse(int_, p, i)) return i; boost::throw_exception(...); } }; template <typename T> T convert(char const* p) { return convert_to<T>::call(p); } int i = convert<int>("1"); You get the idea. 3) What about leading/trailing whitespace in the strings to convert? 4) Default values should be directly associated with the conversion operation in question, any of the syntax' above is misleading. Either have default values as a separate parameter to convert(), or use the explicit syntax proposed before: convert().default(...). 5) Throw/nothrow semantics are _not_ orthogonal to default values, those are directly depending on each other. I.e. if I specify a default value, I don't want to get an exception. The opposite is true as well, if I do want to get an exception, I probably won't specify a default value. But everything is just IMHO... Regards Hartmut