Re: [boost] [convert] Default Syntax Idea

From: "Stewart, Robert" <Robert.Stewart@sig.com> I was thinking a bit about syntax for the default value for the notional boost::convert() and kin. How about the following:
int const i(3); string const s("something"); int const j(convert<int>(s) | 5);
Yes, this idea is similar to int j = convert<int>(s).or_default(5); but does not introduce new vocabulary (my main hesitation). With the suggested op|, as I am reading it, it makes perfect reading just like I'd read English. And that is usually my criterion for an interface. So, I am voting 'yes'... in principle. See further below.
convert<DestType,SourceType>(SourceType) can return a type which throws on failure in operator DestType(), but has an operator |(SourceType) which returns a second type constructed with the SourceType value. The second type provides its own operator |(SourceType) which returns that value in its operator DestType() on failure.
When the user does not want comprehensive result, it works. The problem as I see it is when the user wants the comprehensive report -- we return two different types, convert1<string, int> j1 = convert<string, int>(s); convert2<string, int> j2 = convert<string, int>(s) | 5; Sometimes I feel we need that as I might like to explicitly check if the call failed without relying on the default convert1<string, int> j1 = convert<string, int>(s) & boost::nothrow; convert2<string, int> j2 = convert<string, int>(s) | 5; if (j1 == 0 && !j1.good()) j1 = 0 but conversion failed if (j2 == 5 && !j1.good()) j2 = 5 but conversion failed Unless we convert both convert1 and convert2 to convert<string, int>::result as convert<string, int>::result j1 = convert<string, int>(s) & boost::nothrow; convert<string, int>::result j2 = convert<string, int>(s) | 5; if (j1 == 0 && !j1.good()) j1 = 0 but conversion failed if (j2 == 5 && !j1.good()) j2 = 5 but conversion failed Or we might simplify that as convert::result<int> j1 = convert::to<int>(s) & boost::nothrow; convert::result<int> j2 = convert::to<int>(s) | 5; if (j1 == 0 && !j1.good()) j1 = 0 but conversion failed if (j2 == 5 && !j1.good()) j2 = 5 but conversion failed The reason I am sticking 'to' in again is that I need the 'convert' as a namespace... or rather as a namespace-like structure: struct convert : mimic_namespace /*I have such a class*/ { template<class TypeOut> struct result { TypeOut value_; bool good_; }; struct internal_with_default { ... }; struct internal_with_no_default { ... }; template<class TypeOut, class TypeIn> internal_with_default<TypeOut, TypeIn> to(TypeIn const&); }; With namespace-like convert structure people will not be able to shortcut 'convert'. I.e. they'll always have to say convert::to which is darn close to convert_to with the advantage of having a dedicated namespace-like place instead of messing inside the boost namespace. Now it seems like I am back where I started. :-)
Returning a default value in lieu of an exception is a special case; it ought not be treated like formatting.
I have an impression that throwing/not-throwing and providing/not-providing the default have to be configured independently. Like I *might* need to throw even if I provide the default: direction dir = convert<string, direction>(str, direction::up) & boost::dothrow; Above I provide the default because I have to (direction does not have the def. ctor). However, I still *might* like to throw on failure.
Thus, while Boost. Parameter might be appropriate to package the extra information desired for formatting, I don't like the idea of writing "default_ = 5" in the call, and I don't think ", 5" in the argument list is as clear as it could be.
I tend to agree with you on both.
The syntax above is obvious and concise while being distinct from whatever may be done for formatting.
You meant to say "laconic", right? Oh, just kidding. Thanks for pointing it out BTW. BTW, what do you think of convert<string, int>(s) & boost::dothrow; instead of convert<string, int>(s) >> boost::throw_t(); People seem to get put off by op>>. Best, V.

From: <Vladimir.Batov@wrsa.com.au>
From: "Stewart, Robert" <Robert.Stewart@sig.com> I was thinking a bit about syntax for the default value for the notional boost::convert() and kin. How about the following:
int const i(3); string const s("something"); int const j(convert<int>(s) | 5);
Yes, this idea is similar to
int j = convert<int>(s).or_default(5);
but does not introduce new vocabulary (my main hesitation). With the suggested op|, as I am reading it, it makes perfect reading just like I'd read English. And that is usually my criterion for an interface. So, I am voting 'yes'... in principle.
Now I am not so sure. In isolation it looks good: int j = convert_to<int>(s) | 5; What happens when we start mixing with int j = convert_to<int>(s) | 5 >> std::hex; int j = convert_to<int>(s) | 5 >> boost::dothow >> std::hex; or even int j = convert_to<int>(s) >> boost::dothow >> std::hex | 5; That is, it goes against what you said (and I agree with) -- "Returning a default value ... ought not be treated like formatting". Above we do treat the default like formatting, don't we? Now I feel confused and want back to the comfort of int j = convert_from<string, int>(s, 5) >> std::hex; V.

boost-bounces@lists.boost.org wrote:
Now I am not so sure. In isolation it looks good:
int j = convert_to<int>(s) | 5;
What happens when we start mixing with
int j = convert_to<int>(s) | 5 >> std::hex; int j = convert_to<int>(s) | 5 >> boost::dothow >> std::hex; or even int j = convert_to<int>(s) >> boost::dothow >> std::hex | 5;
To add to the "I am not sure" side: What if I want to perform a bitwise-or on the result of convert_to<int>?

Eric MALENFANT a écrit :
boost-bounces@lists.boost.org wrote:
Now I am not so sure. In isolation it looks good:
int j = convert_to<int>(s) | 5;
What happens when we start mixing with
int j = convert_to<int>(s) | 5 >> std::hex; int j = convert_to<int>(s) | 5 >> boost::dothow >> std::hex; or even int j = convert_to<int>(s) >> boost::dothow >> std::hex | 5;
To add to the "I am not sure" side: What if I want to perform a bitwise-or on the result of convert_to<int> What about being on the safe side with :
int j = convert_to<int>(s) | default_(5); ?? -- ___________________________________________ Joel Falcou - Assistant Professor PARALL Team - LRI - Universite Paris Sud XI Tel : (+33)1 69 15 66 35

On Wednesday, February 25, 2009 9:14 AM Joel Falcou wrote:
Eric MALENFANT a écrit :
boost-bounces@lists.boost.org wrote:
Now I am not so sure. In isolation it looks good:
int j = convert_to<int>(s) | 5;
What happens when we start mixing with
int j = convert_to<int>(s) | 5 >> std::hex; int j = convert_to<int>(s) | 5 >> boost::dothow >> std::hex; or even int j = convert_to<int>(s) >> boost::dothow >> std::hex | 5;
To add to the "I am not sure" side: What if I want to perform a bitwise-or on the result of convert_to<int> What about being on the safe side with :
int j = convert_to<int>(s) | default_(5);
This would be more direct if you're going to include "default_" in the call: convert<int>(s).default_(5); _____ 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 Wednesday, February 25, 2009 9:02 AM Eric MALENFANT wrote:
boost-bounces@lists.boost.org wrote:
Now I am not so sure. In isolation it looks good:
int j = convert_to<int>(s) | 5;
What happens when we start mixing with
int j = convert_to<int>(s) | 5 >> std::hex; int j = convert_to<int>(s) | 5 >> boost::dothow >> std::hex; or even int j = convert_to<int>(s) >> boost::dothow >> std::hex | 5;
To add to the "I am not sure" side: What if I want to perform a bitwise-or on the result of convert_to<int>?
I've just recanted due to operator precedence, though I doubt you'd be very likely to try to use any operator on the result of calling convert(). _____ 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 Wednesday, February 25, 2009 4:44 AM Vladimir Batov wrote:
From: <Vladimir.Batov@wrsa.com.au>
From: "Stewart, Robert" <Robert.Stewart@sig.com>
int const i(3); string const s("something"); int const j(convert<int>(s) | 5);
So, I am voting 'yes'... in principle.
Now I am not so sure. In isolation it looks good:
int j = convert_to<int>(s) | 5;
What happens when we start mixing with
int j = convert_to<int>(s) | 5 >> std::hex; int j = convert_to<int>(s) | 5 >> boost::dothow >> std::hex; or even int j = convert_to<int>(s) >> boost::dothow >> std::hex | 5;
I don't think the throw/nothrow part belongs, but it's still good for illustrating your concern with manipulators. | has a lower precedence than >> (or <<), so those expressions won't work. They'd have to be written as: (convert<int>(s) | 5) >> std::hex; Is that bad? Yes. Many will be frustrated by how easy it is to forget the parentheses and get a compilation error when supplying the default. The alternative to an operator is some named default function or an extra, confusing argument: convert<int>(s).or_(5) >> std::hex; convert<int>(s, 5) >> std::hex; To avoid the precedence problem, the available operators that make any sense at all are: convert<int>(s)[5] >> std::hex; convert<int>(s) ! 5 >> std::hex; convert<int>(s) ~ 5 >> std::hex; convert<int>(s) & 5 >> std::hex; convert<int>(s) / 5 >> std::hex; convert<int>(s) % 5 >> std::hex; The subscript operator is not bad. "[5]" implies that 5 is optional in a number of contexts. Bang suggests using 5 on error. Tilde doesn't evoke "default" in any way, but it also doesn't look like an egregious misuse of the operator. The final three just look wrong to me. Given that the default value calls should be easy to get right and that the precedence issue will bite many, we should select a different operator if we choose to retain an operator for the job. Among the available choices, I favor bang: convert<int>(s) ! 5 >> std::hex; _____ 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.

Stewart, Robert wrote: [snip]
To avoid the precedence problem, the available operators that make any sense at all are:
convert(s)[5] >> std::hex; convert(s) ! 5 >> std::hex; convert(s) ~ 5 >> std::hex; convert(s) & 5 >> std::hex; convert(s) / 5 >> std::hex; convert(s) % 5 >> std::hex;
The subscript operator is not bad. "[5]" implies that 5 is optional in a number of contexts. Bang suggests using 5 on error. Tilde doesn't evoke "default" in any way, but it also doesn't look like an egregious misuse of the operator. The final three just look wrong to me.
Given that the default value calls should be easy to get right and that the precedence issue will bite many, we should select a different operator if we choose to retain an operator for the job. Among the available choices, I favor bang:
convert(s) ! 5 >> std::hex;
How is that? Both ! and ~ were unary operators last time I checked :) Best Regards, Gevorg

On Wednesday, February 25, 2009 9:59 AM Gevorg Voskanyan wrote:
Stewart, Robert wrote:
[snip]
To avoid the precedence problem, the available operators that make any sense at all are:
convert(s)[5] >> std::hex; convert(s) ! 5 >> std::hex; convert(s) ~ 5 >> std::hex; convert(s) & 5 >> std::hex; convert(s) / 5 >> std::hex; convert(s) % 5 >> std::hex;
How is that? Both ! and ~ were unary operators last time I checked :)
Doh! Of course, I knew that, but totally ignored that when compiling the list. That, of course, eliminates the two that I thought actually looked decent. Subscript operator anyone? _____ 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: <Vladimir.Batov@wrsa.com.au>
From: "Stewart, Robert" <Robert.Stewart@sig.com> I was thinking a bit about syntax for the default value for the notional boost::convert() and kin. How about the following:
int const i(3); string const s("something"); int const j(convert<int>(s) | 5);
Yes, this idea is similar to
int j = convert<int>(s).or_default(5);
but does not introduce new vocabulary (my main hesitation). With the suggested op|, as I am reading it, it makes perfect reading just like I'd read English. And that is usually my criterion for an interface. So, I am voting 'yes'... in principle.
Now I am not so sure. In isolation it looks good:
int j = convert_to<int>(s) | 5;
IMHO, operator|() just has the wrong precedence for this kind of syntax as, most of the time, it needs to be put in parenthesis's.
What happens when we start mixing with
int j = convert_to<int>(s) | 5 >> std::hex; int j = convert_to<int>(s) | 5 >> boost::dothow >> std::hex; or even int j = convert_to<int>(s) >> boost::dothow >> std::hex | 5;
Some additional remarks. 1) Streams have proved to be horrible at formatting. Why stick with this scheme? Why not invent something more flexible like: int j = convert_to<hex>(s); ? 2) What about more complex conversions like: int i, j; convert(int_ >> ", " >> int_, "1,2", tie(i, j)); assert(i == 1 && j == 2); which brings us directly into the domain of Spirit (and, if you change convert for parse, you'll almost get a valid spirit expression, btw). So here is my question again. What's the point in duplicating Spirit's functionality? I understand that you strive for a simpler syntax for primitive conversion operations. So, let's think about how to put this simple API on top of Spirit, covering the primitive conversion, but having a well defined migration path towards more complex conversion needs. Converting a simple integer in Spirit is a bit verbose: int i; if (parse(int_, "1", i)) // ... succeeded But OTOH, this nicely integrates with a convert() function to be put on top of it: template <typename T> struct convert_to; template <> struct convert_to<int> { static int call(char const* p) { int i; if (spirit::qi::parse(int_, p, i)) return i; boost::throw_exception(...); } }; template <typename T> T convert(char const* p) { return convert_to<T>::call(p); } int i = convert<int>("1"); You get the idea. 3) What about leading/trailing whitespace in the strings to convert? 4) Default values should be directly associated with the conversion operation in question, any of the syntax' above is misleading. Either have default values as a separate parameter to convert(), or use the explicit syntax proposed before: convert().default(...). 5) Throw/nothrow semantics are _not_ orthogonal to default values, those are directly depending on each other. I.e. if I specify a default value, I don't want to get an exception. The opposite is true as well, if I do want to get an exception, I probably won't specify a default value. But everything is just IMHO... Regards Hartmut

From: "Hartmut Kaiser" <hartmut.kaiser@gmail.com>
Some additional remarks.
1) Streams have proved to be horrible at formatting. Why stick with this scheme? ... 2) What about more complex conversions like: ... which brings us directly into the domain of Spirit (and, if you change convert for parse, you'll almost get a valid spirit expression, btw). So here is my question again. What's the point in duplicating Spirit's functionality? 3) What about leading/trailing whitespace in the strings to convert?
For me the most important property of lexical_cast (where it all started) is the ability to plug my own class in to the framework. I need int i = convert_to<int>(str); direction dir = convert_to<direction>(str); and template<class T> void foo(T const& instance) { ... convert_from(instance); ... } The mechanism of plugging a user-defined class into the lexical_cast/convert framework (with op>>, op<<) might be clumsy. However, it is well understood and readily available. When I looked at Spirit I did not get an impression that I can do that. Can I? As for formatting it is no more limited than IOStream-based formatting. For many (including me) it is sufficient. And I do not believe convert() should be looked at as a comprehensive formatter. For me convert() is a better (much better?) lexical_cast. I have many uses for that. For real formatting I'll use something completely else.
4) Default values should be directly associated with the conversion operation in question, any of the syntax' above is misleading. Either have default values as a separate parameter to convert(), or use the explicit syntax proposed before: convert().default(...).
Yes, I think I've come to a similar conclusion and for the time being I am favoring the original convert_from(string, -1/*the default*/)
5) Throw/nothrow semantics are _not_ orthogonal to default values, those are directly depending on each other. I.e. if I specify a default value, I don't want to get an exception. The opposite is true as well, if I do want to get an exception, I probably won't specify a default value.
I have to disagree here as for a non-DefaultConstructible class one has to provide a default. Like // By default convert_to throws on failure direction dir = convert_to<direction>(str) won't compile as it requires direction::direction(). So, I have to write // By default convert_from does not throw on failure direction dir = convert_from(str, direction::up) >> boost::dothrow; In that setting to get the same (as convert_to) throwing behavior as the default is provided ouf of necessity rather than to shape throwing/non-throwing behavior. Equally I cannot see anything out of ordinary if someone might like convert_to() without the throwing behavior. // By default convert_to throws on failure convert<int>::result i = convert_to<int>(str) >> boost::nothrow; if (i.good()) V.

On Tuesday, February 24, 2009 5:29 PM Vladimir.Batov@wrsa.com.au wrote:
From: "Stewart, Robert" <Robert.Stewart@sig.com>
int const i(3); string const s("something"); int const j(convert<int>(s) | 5);
Yes, this idea is similar to
int j = convert<int>(s).or_default(5);
but does not introduce new vocabulary (my main hesitation). With the suggested op|, as I am reading it, it makes perfect reading just like I'd read English. And that is usually my criterion for an interface. So, I am voting 'yes'... in principle. See further below.
I suggested it precisely because it read well. I'm glad you thought so, too.
convert<DestType,SourceType>(SourceType) can return a type which throws on failure in operator DestType(), but has an operator |(SourceType) which returns a second type constructed with the SourceType value. The second type provides its own operator |(SourceType) which returns that value in its operator DestType() on failure.
When the user does not want comprehensive result, it works. The problem as I see it is when the user wants the comprehensive report -- we return two different types,
convert1<string, int> j1 = convert<string, int>(s); convert2<string, int> j2 = convert<string, int>(s) | 5;
I don't think that's a valid concern. The whole point of supplying a default is that there will always be a valid result. It may be the converted input or it may be the default, but the caller wants one or the other and will proceed apace.
Sometimes I feel we need that as I might like to explicitly check if the call failed without relying on the default
convert1<string, int> j1 = convert<string, int>(s) & boost::nothrow; convert2<string, int> j2 = convert<string, int>(s) | 5;
if (j1 == 0 && !j1.good()) j1 = 0 but conversion failed if (j2 == 5 && !j1.good()) j2 = 5 but conversion failed
I can't imagine a use case for that. If the caller actually wants to know whether the conversion succeeds, the caller can attempt the conversion and if it fails, supply the default and take other actions. There's no need to support both a default and the conversion state.
Unless we convert both convert1 and convert2 to convert<string, int>::result as
convert<string, int>::result j1 = convert<string, int>(s) & boost::nothrow; convert<string, int>::result j2 = convert<string, int>(s) | 5;
if (j1 == 0 && !j1.good()) j1 = 0 but conversion failed if (j2 == 5 && !j1.good()) j2 = 5 but conversion failed
Or we might simplify that as
convert::result<int> j1 = convert::to<int>(s) & boost::nothrow; convert::result<int> j2 = convert::to<int>(s) | 5;
I don't see that adding any value.
if (j1 == 0 && !j1.good()) j1 = 0 but conversion failed if (j2 == 5 && !j1.good()) j2 = 5 but conversion failed
The reason I am sticking 'to' in again is that I need the 'convert' as a namespace... or rather as a namespace-like structure:
struct convert : mimic_namespace /*I have such a class*/ { template<class TypeOut> struct result { TypeOut value_; bool good_; };
struct internal_with_default { ... }; struct internal_with_no_default { ... };
template<class TypeOut, class TypeIn> internal_with_default<TypeOut, TypeIn> to(TypeIn const&); };
Why not spell "to" as "operator ()?"
With namespace-like convert structure people will not be able to shortcut 'convert'. I.e. they'll always have to say
convert::to
Not with a function call operator.
Returning a default value in lieu of an exception is a special case; it ought not be treated like formatting.
I have an impression that throwing/not-throwing and providing/not-providing the default have to be configured independently. Like I *might* need to throw even if I provide the default:
direction dir = convert<string, direction>(str, direction::up) & boost::dothrow;
This just doesn't make sense to me. You emphasized "might" which implies you have no use case in mind and I cannot imagine one.
Above I provide the default because I have to (direction does not have the def. ctor). However, I still *might* like to throw on failure.
That's an interesting use case. An interface that takes a non-const reference to the output value would work. direction dir(direction::up); if (convert(str, dir)) ... The arguments could occur in either order, of course, and I normally put output parameters first, but thought the order shown fit the usual reading of "convert a to b" of such an expression. That could be spelled other ways, too, like, "convert(str).to(dir)." That approach is also like what I have been advocating to avoid implicit output conversions.
BTW, what do you think of
convert<string, int>(s) & boost::dothrow;
instead of
convert<string, int>(s) >> boost::throw_t();
People seem to get put off by op>>.
I don't think either is needed. One interface should imply an exception and another, with a default value, should imply no exception. If forced to choose, I'd say neither as, again, operator |() makes more sense: convert<int>(str) | boost::throw; _____ 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.
participants (7)
-
Eric MALENFANT
-
Gevorg Voskanyan
-
Hartmut Kaiser
-
Joel Falcou
-
Stewart, Robert
-
Vladimir Batov
-
Vladimir.Batov@wrsa.com.au