
From: "Stewart, Robert" <Robert.Stewart@sig.com>
1) with default: return the conversion result if successful, return the provided default if not.
What about callers that want a result object for types without a default constructor? Don't you always have to return the result type?
With or without the def.ctor convert<>::from can return both: direction dir = convert<direction>::from(str, direction::up); convert<direction>::result res = convert<direction>::from( "junk", direction::up);
Does your implementation class provide conversion operators to either the result type or TypeOut.
Yes, the implementation provides conversion to both -- convert<TypeOut>::result and TypeOut.
If so, that complicates the implementation classes which will complicate any further extension.
It did not feel that way. In the latest implementation those implicit conversions are as follows: operator Result () const { return apply_(); } operator TypeOut () const { return apply_().value(); }
My idea is to always return the result type and let it handle the exceptions.
I've implemented "let the result type handle the exceptions" in the latest (uploaded) incarnation. Exceptions are thrown from convert<>::result::value() when appropriate. [snip]
The result object can have three states defined in an enumerated type: all's well, valid value but conversion failed, and conversion failed and no valid value.
I feel with the above policies we already have all three covered -- the with-default conversion request covers the first two (all's well and conversion=failed+value=valid) and the no-default conversion request covers #1 and #3 (all's well and conversion=failed+value=no value).
I don't think it covers everything.
I do not understand. You indicated that "the result object can have three states". I described how (I think) they are covered. What did I miss? [snip]
There are important use cases for each function as I thought I showed.
Apologies if I somehow missed those.
1. valid() indicates whether value() will throw an exception.
The difference in our views seems that you feel valid() is needed to indicate if value() will throw or not. My view is that "will throw or not" is defined by the usage -- no-default will throw (if conversion failed), with-default will not throw. If I were to save convert<>::result and pass it to some other function, I'd agree that valid() would be useful with the anticipated usage convert<int>::result r1 = convert<int>::from(junk); convert<int>::result r2 = convert<int>::from(junk, -1); I immediately see that r1.value() will throw and r2.value() will not. Therefore, I insist that valid() is superfluous.
2. converted() indicates whether the conversion succeeded.
That is provided via implicit converter to safe-bool. I.e. "if (r1)" is the same as "if (r1.converted())".
Each question is distinct and can be used by calling code to understand the result of the conversion request.
I do not dispute that. I only answer these two questions somewhat differently (the valid() one anyway).
When supplying a default for a type with a default constructor, the implication is that the caller wants valid() to be true regardless of converted(), so that the caller can always use the implicit conversion (or call value()).
With the default supplied valid() always returns true. I do not feel like under the conditions the function adds much.
If there is no default constructor, the caller may actually want to know whether the conversion worked, so converted() provides that information.
Yes, it is certainly available. Although the functionality is provided via safe-bool implicit conversion which I think you advocated (I used to call it good()).
When the caller provides no default, either call yields the same information: either the conversion succeeded and value() will not throw, or it failed and value() will throw.
Again, I feel the valid()-returned information is available to me without valid().
... but uniformity of interface is valuable: use the same result type with or without a default.
I do use the same convert<>::result type with or without a default. It's just that its behavior depends on the way convert<>::from was called. [snip]
T t = convert<T>::from(str, def, dothrow); T t = convert<T>::from(str, def) >> dothrow; T t = convert<T, dothrow>::from(str, def);
The more I see it, the more I realize I don't care for "dothrow."
From the context I assume you mean you do not *like* it, right?
It differs from the common "nothrow" by just one letter and doesn't quite convey the intention anyway; there should only be an exception on conversion failure, not in all cases. What do you think of "require?" If we extend the result type with one more member function, required(), we could get what we want without needing dothrow:
int i(convert<int>::from(str, def).required());
Somehow 'required' does not give me a strong enough clue it is about throwing behavior. How about "throw_on_failure". With that I understand we can supply conversion configuration information in many ways: T t = convert<T>::from(str, def).throw_on_failure(); T t = convert<T>::from(str, def) >> throw_on_failure;
except required() throws if !converted(), whereas value() throws if !valid().
Oops, so your required() behaves as convert<>::result::value().
The point is that the caller indicates their wish for an exception when extracting the value rather than when writing the conversion call.
Yep, that's where we diverge. I happen to like registering "their wish for an exception when writing the conversion call" for its simplicity. It's like templates (vs. run-time polymorphism) -- describe your intentions upfront. You seem to favor run-time (after the conversion) configurability with valid, converted, required. [snip]
Anyway, I'm proposing the following interface on the result type:
bool valid() const; // have value for value() to return bool converted() const; // conversion succeeded Out value() const; // return value; throw if !valid() Out required() const; // return value; throw if !converted() operator Out() const; // same as value()
I really hate to be a party-pooper but it feels overwhelming. I usually struggle and uncomfortable with big interfaces with subtle differences as above. With the implemented approach things seem considerably simpler and straightforward: TypeOut const& value() const; operator safebool() const;
Setting the locale, formatting, etc. via the extraction operator is sensible because those operate on a std::ostream. Indicating that the conversion should not throw an exception does not affect the ostream, so it ought not to be applied in the same way.
I interpret manipulators somewhat more liberally (rather than so firmly associated with std::stream) and do not feel I cannot extend the concept onto convert adding more manipulators in the process. Best, V. P.S. Rob, it is a long email and very argumentative. In such circumstances I often manage to get in trouble with my English. If anything sounded offensive, impolite or anything of that sort it honestly was not intentional and I apologize unreservedly.