Boost logo

Boost :

Subject: Re: [boost] [convert] Version in the Vault ready for review.
From: Stewart, Robert (Robert.Stewart_at_[hidden])
Date: 2009-03-30 11:00:08

On Saturday, March 28, 2009 9:34 PM
Vladimir Batov wrote:
> > From: "Stewart, Robert" <Robert.Stewart_at_[hidden]>
> > 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.

What about callers that want a result object for types without a default constructor? Don't you always have to return the result type? Does your implementation class provide conversion operators to either the result type or TypeOut (I don't have the code before me). If so, that complicates the implementation classes which will complicate any further extension. My idea is to always return the result type and let it handle the exceptions.

> 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).

I don't think it covers everything.

> > 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.

There are important use cases for each function as I thought I showed.

1. valid() indicates whether value() will throw an exception.
2. converted() indicates whether the conversion succeeded.

Each question is distinct and can be used by calling code to understand the result of the conversion request. 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()). If there is no default constructor, the caller may actually want to know whether the conversion worked, so converted() provides that information.

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. In that case, the two states are convolved, but uniformity of interface is valuable: use the same result type with or without a default.

> > 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.
> 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);

The more I see it, the more I realize I don't care for "dothrow." 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());

That would be the same as this:

   int i(convert<int>::from(str).value());

except required() throws if !converted(), whereas value() throws if !valid(). The point is that the caller indicates their wish for an exception when extracting the value rather than when writing the conversion call. The only thing that doesn't permit is using the implicit conversion, but I'm not sure that's a bad thing as it doesn't seem such a common use case. Then again, maybe that's an argument for the third argument.

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()

> 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. :-)

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.

Rob Stewart robert.stewart_at_[hidden]
Software Engineer, Core Software using std::disclaimer;
Susquehanna International Group, LLP

IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Boost list run by bdawes at, gregod at, cpdaniel at, john at