Boost logo

Boost :

Subject: Re: [boost] [convert] Version in the Vault ready for review.
From: Vladimir Batov (batov_at_[hidden])
Date: 2009-04-01 02:21:34


> From: "Stewart, Robert" <Robert.Stewart_at_[hidden]>
> [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.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk