Subject: Re: [boost] [convert] Version in the Vault ready for review.
From: Stewart, Robert (Robert.Stewart_at_[hidden])
Date: 2009-03-26 10:31:32
On Thursday, March 26, 2009 9:25 AM
Vladimir Batov wrote:
> > From: "Stewart, Robert" <Robert.Stewart_at_[hidden]>
> >> Vladimir.Batov_at_[hidden] wrote:
> Rob, thank you for your continuous help and involvement.
> > I wonder about making the result returning interface explicit.
> > That is, change the two interfaces shown above to return
> > TypeOut and add a new call, something like the following,
> > that returns a result instance:
> > convert<int>::result result(convert<int>::try_from(str));
> > That eliminates all conversion, template deduction,
> > and temporary object overhead issues for the first two calls.
> I am not sure I understand as temporaries seem necessary as
> I'd like to be
> able to do
> double d01 = convert<double>::from(str);
> double d02 = convert<double>::from(str)
> >> new_locale
> >> std::setprecision(4)
> >> std::scientific;
Quite right. I completely ignored the formatting when thinking about that.
> > I think the result type's conversion functions should throw
> > an exception if !good(). Then, there's no need for your dothrow:
> > convert<direction>::result
> > result(convert<direction>::try_from(str, up_dir));
> > direction dir(result); // throws exception if !result.good()
> Unfortunately, that will make my life quite difficult...
> unless I wrap such a convert() into another interface
> eliminating those exceptions. :-) First, my company's policy
> is very unfavorable towards exceptions so I'd like to have
> that no-throw flexibility available for myself and anyone in
> a similar situation. Secondly, my deployment pattern is such
> that I read dozens of config. parameters and try converting
> them. If (more like "when") a conversion fails, I log a
> message for that failed parameter, use the default and move
> on. Like
> convert<type1>::result p1 = convert<type1>::from(str1, def1);
> convert<typeN>::result pN = convert<typeN>::from(strN, defN);
> if (!p1.good()) message("bad str1");
> if (!pN.good()) message("bad strN");
> ... proceed with whatever parameters I've got.
I had no idea you wanted to inspect the conversion status *and* fall back on a default. However, I do recognize that by supplying a default, you've guaranteed a good result, but I was thinking that the same result type was returned whether supplying the default or not. I was thinking of the no-default case and thinking it was the result type's job to throw an exception. That's not what you've done, so we were talking past each other.
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. 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. Then, value() and the conversion operator can throw an exception on !valid(). That is, throw an exception when there is no valid value. 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.
That provides the interface you want:
convert<type1>::result r1(convert<type1>::from(str1, def1));
Here's a non-throwing, no default use case:
// use result.value()
// deal with failure
(I'm assuming the Safe-Bool Idiom approach to report valid(). That should be included.)
Of course, the throwing variation still works:
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. 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));
T t(convert<T>::from(str, def) >> dothrow);
I don't like using a manipulator for such a purpose though.
For the other case, I was thinking that the result type could provide a member function to throw an exception on !converted() just as if value() had been called with !valid():
convert<T>::result r(convert<T>::from(str, def));
r.require_conversion(); // throws if !converted()
That isn't as succinct, but it makes this special case obvious without yet another overload or an oddball manipulator.
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