Boost logo

Boost :

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


> From: "Stewart, Robert" <Robert.Stewart_at_[hidden]>

> ... I was thinking that the same result type was returned whether
> supplying the default or not.

Yes, indeed the same result is returned.

> I was thinking of the no-default case and thinking it was the result
> type's job to throw an exception.

Yes, I agree. It was an oversight on my part. Now that you mentioned that I
feel that for both return types -- the "naked" result and wrapped in
convert<>::result -- we should specify our policies consistently as follows:

1) with default: return the conversion result if successful, return the
provided default if not.
2) with no default: return the conversion result if successful, throw if not
as we have no value to return.

Returning convert<>::result does *not* change the behavior above but only
delay their application/execution. Therefore, the no-default conversion
request will behave as follows:

// value requested. throw if failed as no value to return
int i1 = convert<int>::from(str);

// does not throw yet
convert<int>::result res = convert<int>::from(str);
// value requested. throw if failed as no value to return
int i2 = res.value();

// non-throwing processing
if (res) // safe to retrieve the value
{
    int i2 = res.value();
}

> I have another idea. If the result type were to throw the exception when
> needed, then the conversion code can just return a bool used to initialize
> the result object's state. 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).

> There can be two
> separate state accessors, say, valid() and converted(), which will convey
> all known conversion information to the client.
>
> With that approach, when there's a default value, valid() will always
> return true, but converted() may not. When there's no default value,
> valid() and converted() will always return the same value: either the
> conversion succeeds and there's a valid value, or it failed and there
> isn't a valid value.

I am sorry but my initial reaction is that it feels like an overkill as
'valid' does not seem exactly useful -- for with-default "it will always
return true" and for no-default "if will always return the same" as the
'converted' result.

> ... throw an exception when there is no
> valid value.

Yes, I think that should be our consistent policy for both result types
returned (naked and wrapped in convert<>::result).

> Thus, the behavior is the same as you now have for the
> throwing case, but moves the exception throwing to the result object.
> However, the non-default overload needn't throw an exception on failure
> unless the caller tries to extract the missing value, so there's no need
> for the dothrow/nothrow manipulator.

I agree with your description and nothrow can go now. I'd like to get rid of
'dothrow' as well but that seems still needed for the case when one wants
the throwing behavior but have to use the with-default conversion due to the
type not having the default constructor.

> ...
> What that doesn't cover is the case when the default is supplied only
> because the type doesn't support default construction and an exception is
> still wanted.

I feel we are covered as long as we keep 'dothrow' around.

> There should be a way to indicate that the "default"
> argument isn't to be used to make the result valid or to trigger the
> exception when the conversion failed.
>
> For the former, there are at least two possibilities:
>
> T t(convert<T>::from(str, def, dothrow));
>
> and:
>
> T t(convert<T>::from(str, def) >> dothrow);
>
> I don't like using a manipulator for such a purpose though.

To supply that dothow I've been looking as 3 variations:

T t = convert<T>::from(str, def, dothrow);
T t = convert<T>::from(str, def) >> dothrow;
T t = convert<T, dothrow>::from(str, def);

On one hand, I understand that #2 might initially look like an oddball
manipulator. On the other hand, op>>() in not *owned* by IO Streams and I am
content supplying the locale that way:

double d02 = convert<double>::from(str, 0)
>> new_locale
>> std::scientific;

After that another step to

double d02 = convert<double>::from(str, 0)
>> new_locale
>> std::scientific
>> dothrow;

seems (at least formally) logical as we *uniformly* shape conversion
behavior with op>>() and convert-manipulators (some of which happened to be
io-stream manipulators as well). IMHO consistency is more important than the
oddball nature of the dothrow manipulator as IMHO consistency breeds
familiarity, which breeds confidence. As for "oddball" impression, it'll
pass -- I felt that way many times first time looking as boost libraries.
:-)

Best,
V.


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