[convert] Version in the Vault ready for review.

After listening to many views, opinions, suggestions I've re-done boost::convert and would like it scheduled for a review. It is certainly nowhere as MPL and still not perfect. However, I believe it will be very useful to many developers (like me) not satisfied with lexical_cast and not (yet?) needing (or ready for) something like upcoming Spirit 2.0. Overall, after weighing all pros and cons, I am reasonably happy with the current interface and implementation. It does not implement all that have been discussed and suggested. It is most unlikely implemented the way some would like it to see. However, I had to make choices I felt comfortable with and I feel the current interface is minimal, readable and allows for further growth. With that review-request I would like to avoid opening up another round of discussions if 'from' was a good, bad, mediocre choice. I feel we've exhausted that topic and I would like people to simply vote if they find the functionality needed. I am most certainly prepared to eat my humble pie if the vote is 'no'. Still, I am very much looking forward to any implementation-related suggestions/corrections or new ideas we have not discussed so far. The current interface can be summed up as with as little as int i = convert<int>::from(str); // Throws on failure int i = convert<int>::from(str, -1); // Returns -1 on failure More details can be found in the accompanying document (just remember I am not Shakespeare). If/when we decide to extend that by incorporating, say, Boost.Parameter and/or Spirit, then it might be as int i = convert<int>::from(str)(radix_ = 10, more_parm_ = ...); int i = convert<int>::from(str)(some Spirit related stuff); I am prepared to look into it further... if this initial proposal is accepted... otherwise, it'll be less work for me. :-) Thank you everyone who participated in the lexical_cast/convert discussions. Your views, opinions, suggestions were very helpful and educating and are very much appreciated. I hope I did not forget anyone in the "acknowledgements" list. The file called boost-string-convert.zip can be found in the Vault at http://www.boostpro.com/vault/. Thanks, Vladimir.

Is it possible to schedule a review for the Boost.Convert? Thanks, Vladimir. From: <Vladimir.Batov@wrsa.com.au> Sent: Monday, March 02, 2009 11:02 AM Subject: [convert] Version in the Vault ready for review.
After listening to many views, opinions, suggestions I've re-done boost::convert and would like it scheduled for a review.
It is certainly nowhere as MPL and still not perfect. However, I believe it will be very useful to many developers (like me) not satisfied with lexical_cast and not (yet?) needing (or ready for) something like upcoming Spirit 2.0.
Overall, after weighing all pros and cons, I am reasonably happy with the current interface and implementation. It does not implement all that have been discussed and suggested. It is most unlikely implemented the way some would like it to see. However, I had to make choices I felt comfortable with and I feel the current interface is minimal, readable and allows for further growth. ... The current interface can be summed up with as little as
int i = convert<int>::from(str); // Throws on failure int i = convert<int>::from(str, -1); // Returns -1 on failure ... If/when we decide to extend that by incorporating, say, Boost.Parameter and/or Spirit, then it might be as
int i = convert<int>::from(str)(radix_ = 10, more_parm_ = ...); int i = convert<int>::from(str)(some Spirit related stuff); ... Thank you everyone who participated in the lexical_cast/convert discussions. Your views, opinions, suggestions were very helpful and educating and are very much appreciated. I hope I did not forget anyone in the "acknowledgements" list.
The file called boost-string-convert.zip can be found in the Vault at http://www.boostpro.com/vault/.

From: "Scott McMurray" <me22.ca+boost@gmail.com>
Vladimir Batov <batov@people.net.au> wrote:
Is it possible to schedule a review for the Boost.Convert?
Doesn't it need a review manager first?
By "to schedule" I meant "to register/put into the schedule/queue list (http://www.boost.org/community/review_schedule.html). Entries do initially not have review managers and/or dates. I could be wrong though. Best, V.

On Sunday, March 01, 2009 7:02 PM Vladimir.Batov@wrsa.com.au wrote:
it will be very useful to many developers (like me) not satisfied with lexical_cast and not (yet?) needing (or ready for) something like upcoming Spirit 2.0.
It does seem as if the middle ground would be useful.
The current interface can be summed up as with as little as
int i = convert<int>::from(str); // Throws on failure int i = convert<int>::from(str, -1); // Returns -1 on failure
More details can be found in the accompanying document (just remember I am not Shakespeare).
There's room to improve the document, of course, but it is a very nice start. The result type's documentation is rather hidden, unfortunately, as it appears in the "Conversion-Failure Check" section. It should be prominently featured in one of the early sections. 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. It also makes explicit when there's a result object. 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() I dislike the nothrow idea since it implicitly provides a default of TypeOut() and there's already an interface for specifying a default. I'd rather folks wrote this: int i(convert<int>::from("not an int", 0)); than this: int i(convert<int>::from("not an int") >> boost::nothrow); Your locale example fails to break a line: std::locale...// Windows-style locale d1 = convert... which should be: std::locale new_locale(...); // Windows-style locale string d1 = convert<string>::from(d) I have some issues with the implementation, but I'll defer to another time. _____ Rob Stewart robert.stewart@sig.com 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.

From: "Stewart, Robert" <Robert.Stewart@sig.com>
Vladimir.Batov@wrsa.com.au wrote:
Rob, thank you for your continuous help and involvement. Pouring over the docs and implementation takes considerable time and effort and I much appreciate your doing so.
...
The current interface can be summed up as with as little as
int i = convert<int>::from(str); // Throws on failure int i = convert<int>::from(str, -1); // Returns -1 on failure
There's room to improve the document, of course, but it is a very nice start. The result type's documentation is rather hidden, unfortunately, as it appears in the "Conversion-Failure Check" section. It should be prominently featured in one of the early sections.
Thanks, I'll look into addressing it.
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));
Andrew Troschinetz mentioned that it'd be nice settling on only one interface and I was very happy when the interface shrunk to just one int i = convert<int>::from(... Therefore, at the moment my preference would be to try hard keeping that only entry point and to try to extend the functionality by other means if needed.
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; To have the above I seem to need a temporary (in the implementation it is convert_detail::implementation<TypeIn, TypeOut>) to delay the actual conversion.
It also makes explicit when there's a result object.
I am honestly confused as I do not believe there are separate cases when a convert<>::result object exists or not. The conversion specification is collected into a convert_detail::implementation temporary. Then, the temporary can equally return the "naked" conversion value (like "int") or convert<int>::result depending on the user preference: int i1 = convert<int>::from(str, 0); convert<int>::result i2 = convert<int>::from(str, 0); I might be missing something but at present I fail to see benefits of additional "try_from".
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. The above is admittedly uninspiring but I found people to be comfortable with it. Throwing exceptions in seem to complicate the issue considerably. On the other hand I see your point as *not* throwing in direction dir(result); allows the conversion failure to slip through unnoticed. I am not sure how serious it is. I am kind of leaning towards these two int i1 = convert<int>::from(str, 0); convert<int>::result i2 = convert<int>::from(str, 0); behaving the *same* with the second line only adding the *ability* to check the failure condition (it admittedly fits my usage pattern as mentioned above). After all, I provided the default for a reason. If I wanted the throwing behavior, I would not bother providing the default and, therefore, would use the throwing: int i1 = convert<int>::from(str);
I dislike the nothrow idea since it implicitly provides a default of TypeOut() and there's already an interface for specifying a default. I'd rather folks wrote this:
int i(convert<int>::from("not an int", 0));
than this:
int i(convert<int>::from("not an int") >> boost::nothrow);
Yes, I think it is a very sensible observation. I added that nothrow only for symmetry sake but with two lines above side-by-side the latter seems obfuscated and plain ugly. I've removed it from the doc. and will think how to take advantage of no nothrow in the implementation.
Your locale example fails to break a line:
Thanks.
I have some issues with the implementation, but I'll defer to another time.
I'd most likely be very interested in any implementation issues you might see. Although, if that is not an actual bug, then I could probably wait a little longer. :-) Thanks again, V.

On Thursday, March 26, 2009 9:25 AM Vladimir Batov wrote:
From: "Stewart, Robert" <Robert.Stewart@sig.com>
Vladimir.Batov@wrsa.com.au wrote:
Rob, thank you for your continuous help and involvement.
You're welcome.
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)); [snip] 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)); if (!r1.converted())) { log("bad str1"); } type1 p1(r1.value()); Here's a non-throwing, no default use case: convert<T>::result result(convert<T>::from(str)); if (result) { // use result.value() } else { // 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: T t(convert<T>::from(str)); 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)); and: 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() T t(r.value()); That isn't as succinct, but it makes this special case obvious without yet another overload or an oddball manipulator. _____ Rob Stewart robert.stewart@sig.com 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.

From: "Stewart, Robert" <Robert.Stewart@sig.com>
... 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.

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

From: "Stewart, Robert" <Robert.Stewart@sig.com>
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?
With or without the def.ctor convert<>::from can return both: direction dir = convert<direction>::from(str, direction::up); convert<direction>::result res = convert<direction>::from( "junk", direction::up);
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(); }
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. [snip]
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? [snip]
There are important use cases for each function as I thought I showed.
Apologies if I somehow missed those.
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 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.
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())".
Each question is distinct and can be used by calling code to understand the result of the conversion request.
I do not dispute that. I only answer these two questions somewhat differently (the valid() one anyway).
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.
If there is no default constructor, the caller may actually want to know whether the conversion worked, so converted() provides that information.
Yes, it is certainly available. Although the functionality is provided via safe-bool implicit conversion which I think you advocated (I used to call it good()).
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.
Again, I feel the valid()-returned information is available to me without valid().
... but uniformity of interface is valuable: use the same result type with or without a default.
I do use the same convert<>::result type with or without a default. It's just that its behavior depends on the way convert<>::from was called. [snip]
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?
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 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().
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. [snip]
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. With the implemented approach things seem considerably simpler and straightforward: TypeOut const& value() const; operator safebool() const;
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. Best, V. 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.

On Tuesday, March 31, 2009 6:01 AM Vladimir Batov wrote:
From: "Stewart, Robert" <Robert.Stewart@sig.com>
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@sig.com 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.

From: "Stewart, Robert" <Robert.Stewart@sig.com> [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.

On Wednesday, April 01, 2009 2:22 AM Vladimir Batov wrote:
From: "Stewart, Robert" <Robert.Stewart@sig.com>
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.
My version can be written like this: Foo f(convert<Foo>::from(str, def).required());
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.
Consider an enlarged variation of those use cases for a moment. Imagine code that invokes a callback or hook to produce a conversion result, the return value of the callable type. If the conversion fails, the code must report the error, throw some special exception, maybe even loop to try again. If the conversion succeeds, the value is stored somewhere or otherwise used. Whether the conversion uses a default is irrelevant. Now imagine similar code that invokes a callback or hook to get a conversion result but only acts if there's an available value. The calling code is ignorant of whether there's a default, but it must avoid an exception. I can imagine these use cases apply to processing user input, configuration file input, or just collections of callbacks or hook functions used for application customization.
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.
They seem like valuable use cases to me.
... 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.
If those use cases prove necessary later, how will you retrofit them to your design?
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.
That there are more complicated things in Boost, I'd agree, but I don't know what surprises, of this sort, you think exist. Did you note my reference to the stream's exception behavior? I was referring to std::basic_ios::exceptions(). The manipulator, whether you call it "throw_on_failure" or something else, would seem to imply a call to std::basic_ios::exceptions() rather than to affect the conversion object, so the difference in expectations and reality will be a surprise.
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();
That latter must be on the result type and is no different than the required() I suggested.
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.
convert *uses* std::ostream-based operations for the conversion. How can you suggestion it might not be present? Those operations are prominent in making convert work, and any formatting the caller applies will be through manipulators, so the different target of your throw_on_failure manipulator must be understood. _____ Rob Stewart robert.stewart@sig.com 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.

Sorry if I skimmed over some of the conversation too quickly, but if not already mentioned, I'd like to suggest passing in the result as a (by ref) param when you want more status information: A) int i = convert<int>::from(str); // throws if necessary B) int i = convert<int>::from(str, 0); // default supplied so no throw C) convert<int>::result res; int i = convert<int>::from(str, res); // no throw - error/status result returned in 'res' int i = convert<int>::from(str, 0, res); // default + status I could also see using nothrow/dothrow in there somwhere, but it seems redundant. Tony

On Monday, April 06, 2009 1:23 PM Gottlob Frege wrote:
Sorry if I skimmed over some of the conversation too quickly, but if not already mentioned, I'd like to suggest passing in the result as a (by ref) param when you want more status information:
A) int i = convert<int>::from(str); // throws if necessary
B) int i = convert<int>::from(str, 0); // default supplied so no throw
C) convert<int>::result res; int i = convert<int>::from(str, res); // no throw - error/status result returned in 'res' int i = convert<int>::from(str, 0, res); // default + status
I've been pondering this suggestion. I like that from() always returns a value and that the converted value isn't part of the result object when using one. I also like the clarity of using overloading to select the result-producing calls. I don't care much for having to default construct the result to pass it to the call, however, but it simplifies the reasoning needed to determine whether an exception will be thrown.
I could also see using nothrow/dothrow in there somwhere, but it seems redundant.
dothrow is only useful in the case when the destination type has no default constructor because your case B) doesn't throw because of the default, while A) won't work for such a type. If the result type provides a means to generate the same exception as would convert<int>::from(str), then there's no need for dothrow. That is, the caller can inspect the result object and trigger the exception on conversion failure, perhaps calling a function that does both. However, that's requires extra code, whereas including dothrow in the expression somehow, makes it part of the conversion. nothrow isn't useful in this scheme. There can be no useful return value for a nothrow call without a default or the ability to determine that the conversion failed. Passing a default or a result object handles those cases. I think this is a nice scheme overall. _____ Rob Stewart robert.stewart@sig.com 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.

On Wed, Apr 8, 2009 at 4:59 PM, Stewart, Robert <Robert.Stewart@sig.com> wrote:
On Monday, April 06, 2009 1:23 PM Gottlob Frege wrote:
A) int i = convert<int>::from(str); // throws if necessary
B) int i = convert<int>::from(str, 0); // default supplied so no throw
Let's split case 'C' into 'C' and 'D':
C) convert<int>::result res; int i = convert<int>::from(str, res); // no throw - error/status
D)
result returned in 'res' int i = convert<int>::from(str, 0, res); // default + status
I've been pondering this suggestion. I like that from() always returns a value and that the converted value isn't part of the result object when using one. I also like the clarity of using overloading to select the result-producing calls. I don't care much for having to default construct the result to pass it to the call, however, but it simplifies the reasoning needed to determine whether an exception will be thrown.
Case 'C' is problematic - let's get rid of it: i = from(str, res); is difficult when the type is NOT default constructible. So let's get rid of it. If you want a status result, supply a default (case D): i = from(str, 0, res); *Alternatively*, since we know the type, we could probably use some type-inference tricks to only allow case C to compile when the type is default constructible: C1: int i = convert<int>::from(str, res); // OK - compiles as int is default constructible. - does NOT throw as you get status back in res. C2: class C { public: C(int, int); }; C c = convert<int>::from(str, res); // does NOT compile C c = convert<int>::from(str, C(1, 2), res); // default required ? Tony

On Thursday, April 09, 2009 11:05 AM Gottlob Frege wrote:
On Wed, Apr 8, 2009 at 4:59 PM, Stewart, Robert <Robert.Stewart@sig.com> wrote:
On Monday, April 06, 2009 1:23 PM Gottlob Frege wrote:
A) int i = convert<int>::from(str); // throws if necessary
B) int i = convert<int>::from(str, 0); // default => no throw
Let's split case 'C' into 'C' and 'D':
C) convert<int>::result res; int i = convert<int>::from(str, res); // no throw - error/status
D)
result returned in 'res' int i = convert<int>::from(str, 0, res); // default + status
Case 'C' is problematic - let's get rid of it: i = from(str, res); is difficult when the type is NOT default constructible. So let's get rid of it. If you want a status result, supply a default (case D): i = from(str, 0, res);
*Alternatively*, since we know the type, we could probably use some type-inference tricks to only allow case C to compile when the type is default constructible:
C1: int i = convert<int>::from(str, res); // OK
- compiles as int is default constructible. - does NOT throw as you get status back in res.
C2: class C { public: C(int, int); }; C c = convert<int>::from(str, res); // does NOT compile C c = convert<int>::from(str, C(1, 2), res); // default required
I'm in favor of keeping C) for symmetry. Since from() is a member function of a class template, it will not be instantiated unless used and, if used, it will fail to compile. Thus, employing Boost.ConceptCheck can produce a helpful error message to explain the problem. _____ Rob Stewart robert.stewart@sig.com 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.

on Thu Apr 09 2009, Gottlob Frege <gottlobfrege-AT-gmail.com> wrote:
Case 'C' is problematic - let's get rid of it: i = from(str, res); is difficult when the type is NOT default constructible. So let's get rid of it. If you want a status result, supply a default (case D): i = from(str, 0, res);
*Alternatively*, since we know the type, we could probably use some type-inference tricks to only allow case C to compile when the type is default constructible:
C1: int i = convert<int>::from(str, res); // OK
- compiles as int is default constructible. - does NOT throw as you get status back in res.
C2: class C { public: C(int, int); }; C c = convert<int>::from(str, res); // does NOT compile C c = convert<int>::from(str, C(1, 2), res); // default required
You might want to take a look at http://www.boost.org/doc/libs/1_38_0/libs/python/doc/v2/extract.html which had to solve some of the same problems. -- Dave Abrahams BoostPro Computing http://www.boostpro.com
participants (6)
-
David Abrahams
-
Gottlob Frege
-
Scott McMurray
-
Stewart, Robert
-
Vladimir Batov
-
Vladimir.Batov@wrsa.com.au