|
Boost : |
Subject: Re: [boost] [convert] Version in the Vault ready for review.
From: Stewart, Robert (Robert.Stewart_at_[hidden])
Date: 2009-03-31 12:05:28
On Tuesday, March 31, 2009 6:01 AM
Vladimir Batov wrote:
> > From: "Stewart, Robert" <Robert.Stewart_at_[hidden]>
> >
> > 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(); }
That's certainly not complicated. ;-) I was imagining more duplication.
> > 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.
I had asked whether you "always return the result type", but you don't, though I see you do rely on it to throw the exception via apply_().value().
> >> > 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?
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));
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());
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
}
5. Caller wants generic code to react to a valid value consistently for all types:
template <class T>
...
convert<T>::result r(/* call other code */);
if (r.valid())
{
// use r.value()
}
6. Caller wants generic code to react to a failed conversion consistently for all types:
template <class T>
...
convert<T>::result r(/* get result somehow */);
if (!r.converted())
{
// react to failure in standard way
}
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.
> > 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
Why isn't that a valid use case?
> 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.
Can you see, from use case 3 particularly, that your interface is lacking?
> > 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())".
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.
> > 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.
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. If that code needs to know whether the conversion succeeded, then converted() indicates that, regardless of whether there was a default. If the code needs to know whether there's a valid value, valid() indicates that, regardless of whether there was a default.
> >> 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?
Yes, for the reasons enumerated:
> > 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
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."
> 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().
Don't convolve your approach with mine when evaluating mine; it just confuses matters. valid() and converted() represent different information. In my approach, value() throws on !valid(), and required() throws on !converted(). They both throw the same exception (which, ideally, includes information about the source and destination types, and the source value), so that the convert user needn't take any other special action to get consistent exceptions.
> > 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.
I favor supporting all use cases.
> > 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.
The interface is neither large nor subtle to me. Simple use cases, 1) and 2) above, are straightforward. Other use cases are supported.
> With the implemented approach
> things seem considerably simpler and straightforward:
>
> TypeOut const& value() const;
> operator safebool() const;
If that were complete, it would indeed be better.
> > 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.
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.
> 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.
If there was anything of the sort, I was blind to it, knowing your intentions as I now do.
_____
Rob Stewart robert.stewart_at_[hidden]
Software Engineer, Core Software using std::disclaimer;
Susquehanna International Group, LLP http://www.sig.com
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 acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk