
From: "Stewart, Robert" <Robert.Stewart@sig.com> [snip] I'll try to clarify. 1. Caller wants to get an exception on failure: int i(convert<int>::from(str)); 2. Caller wants to use a default if the conversion fails, thus never getting an exception: int i(convert<int>::from(str, -1));
Yes. #1&2 are the current interface.
3. Caller wants to convert to a target type having no default constructor, getting an exception on failure as if there had been no default supplied:
Foo def(/* something appropriate */); convert<Foo>::result r(convert<Foo>::from(str, def)); Foo f(r.required());
The current interface does it with Foo foo = convert<Foo>::from(str, def) >> throw_on_failure/dothrow; which I personally find easier to read as there is no convert<>::result-related noise.
4. Caller wants no exception, but wants to know whether the conversion succeeded:
convert<int>::result r(convert<int>::from(str)); if (!r.converted()) { // react to failed conversion }
The current interface does it in a similar fashion: convert<int>::result r = convert<int>::from(str); if (!r) ... // react to failed conversion
5. Caller wants generic code to react to a valid value consistently for all types: convert<T>::result r(/* call other code */); if (r.valid())
6. Caller wants generic code to react to a failed conversion consistently for all types: convert<T>::result r(/* get result somehow */); if (!r.converted())
Yes, these #5&6 use-cases are better catered by the interface you advocate. It might be that I indirectly indicated that I do not feel like encouraging this interface. I see convert<>::result as a very transient on-the-spot token to get more information. I do not feel passing it around is a good and helpful idea. You obviously think differently.
There are other variations on these ideas, but I hope you start to get the idea and the need for the interface I've suggested.
Yes, I've got the idea and I see the need for the interface you are suggesting... if we are to endorse the outlined #5&6 use-cases. In theory #5&6 cases are valid, in reality I personally and others in my vicinity have not been using and needing those (otherwise, I'd have no choice but to implement the functionality already). Therefore, I do not feel like I am prepared to pay that price... yet.
... If I were to save convert<>::result and pass it to some other function, I'd agree that valid() would be useful
Why isn't that a valid use case?
Because I do not feel like encouraging spreading the evaluation of the conversion all over the place (by passing convert::result around). I see convert as a very direct local operation rather than via some function. It might be a limited view but it served me well so far.
convert<int>::result r1 = convert<int>::from(junk); convert<int>::result r2 = convert<int>::from(junk, -1);
Can you see, from use case 3 particularly, that your interface is lacking?
#3 is covered by Foo foo = convert<Foo>::from(str, def) >> throw_on_failure;
If there are two questions to ask of the result object (valid value and successful conversion), then using safe bool as a proxy for successful conversion will be confusing.
Agreed. But you might have noticed I am resisting having to ask two questions. ;-)
Do you see the value now? It indicates whether there is a valid value to extract. Code using the result need not know how the result object was created.
I do indeed now understand your view and see your point. And I'd agree with your proposed interface if I shared your view (namely need to support #5&6) but in this particular case I don't. I am sorry. [snip]
Somehow 'required' does not give me a strong enough clue it is about throwing behavior. How about "throw_on_failure". With that I
If something is required, but not available, what do you expect to happen? It could imply a failed assertion, returning a failure code, or throwing an exception. That required() is called on the result object clearly indicates that the value is required at the time of the call. One sentence of documentation easily reveals that required() throws an exception if the conversion fails, so I don't think the verbose "throw_on_failure" is needed. Indeed, the latter implies manipulating the conversion process rather than the value extraction process, as does "required."
We clearly see it differently as in my book the relevant behavior and configuration information are centered around conversion operation itself. Therefore, I see that throw_on_failure or dothrow belong with convert::from and convert::result is very much on-the-spot helper. You see convert::result coming to the fore. Therefore, in your book more (and more) weight is given to convert::result (that I resist).
... I favor supporting all use cases.
Yes, I understand. It's a noble approach... and expensive. I am a pragmatic. I want to get as much as I *need* for as little as possible and do not want to pay for what I do not need. When I do, I'll reconsider. Maybe it is a short-sighted approach but quite often that 'when' never comes. In this particular case I just do not see #5&6 coming. If they are, I'll probably resist them. Not because they do not fit the current convert but because I do feel it is correct design-wise.
The interface is neither large nor subtle to me. Simple use cases, 1) and 2) above, are straightforward. Other use cases are supported.
I feel that the support comes at the expense of convert::result getting complex... the complexity I am unlikely to ever use.
TypeOut const& value() const; operator safebool() const;
If that were complete, it would indeed be better.
Understood. It seems sufficient for my use-cases. I hear you saying that my cases are not everyone's cases. Theoretically, I agree. In practice, we all go by our *own* experiences.
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.
I've never seen manipulators used for any other purpose and I think most would be surprised at their being used any other way. Thus, it would be confusing to think that a dothrow manipulator did not affect the ostream's exception throwing behavior but instead affected the convert<>::from() call.
I hear you. I truly do. I just feel you are painting the situation somewhat darker than it really is. I do not believe the "surprise" will be such a big shock. Boost is full of far bigger surprises. As I indicated we can pass that throwing information differenty. Like int i = convert<int>::from(str, -1)(throw_on_failure); int i = convert<int>::from(str, -1).throw_on_failure(); I found int i = convert<int>::from(str, -1) >> throw_on_failure; the least objectionable (to me) as it re-uses already deployed op>>() and syntax. I.e. the same op>>() and syntax to configure the conversion. I am not trying to look under convert() and see std::stream there. As a user I care for conversion and not what *might* be under the hood. After all, std::stream might *not* be there. Best, V.