
----- Original Message ----- From: "Vladimir Batov" <vladimir.batov@wrsa.com.au> To: <boost@lists.boost.org> Sent: Tuesday, July 07, 2009 12:03 AM Subject: Re: [boost][convert] are you mixing default_value and error_value?
vicente.botet <vicente.botet <at> wanadoo.fr> writes: I have read the documentation and I think that you are mixing default value with error value.
I am not sure I can agree. In my book there is *no* separate default value. There is only one value -- the conversion-failure (called default, error, etc.) value.
The fact that a type is not DefaultConstructible do not implies that the user wants to have an exception when the conversion fails. This are two orthogonal dimensions.
I think you are saying that "The fact that a type is not DefaultConstructible does not mean that the user *does not* want to have an exception when the conversion fails" because when the default/error value is provided, there is *no* exception on failure.
Yes, this is what I wanted to say. On the case of the direction class, you are forced to give an *initial value* because direction is not default constructible, but with your interface we can't have an exception in this case. Is for this reason I say you are mixin both.
And indeed, I strongly agree that these two issues -- the throwing behavior and providing/not-providing the default/error value -- are indeed orthogonal. I am very glad you mention that because IMHO it is a subtle but important issue that is often missed or misunderstood.
So, do you plan to separate them?
I think that for these non Default constructible types you can use a trait that gives you a default value for types not having a default constructor. E.g.
template <typename T> struct default_value_trait { static const T value=T(); };
template <> struct default_value_trait<direction> { static const direction value=direction::up; };
So now you don't need any more to pass the default value as parameter, and the following should compile
direction dir = convert<direction>::from (str);
and could throw if conversion fails after minor modification on the implementation.
Yes, it is an interesting approach and I like it as it allows to deploy throwing 'convert' uniformly for DefaultConstructible and NonDefaultConstructible types. The only hesitation that pops to my mind is that I feel 'convert' is fairly straightforward and needs to be used without much/any preparation. (That's how I tend to use it anyway). That's why I was whining about the named parameters' interface as it requires a round trip to the top of the file to add 'using'. If lazy-me has to do the same for the default_value_trait<direction> specialization, then I'll probably try avoiding that.
This is surely because you are mixing the returned value when a failure occur and the initial value when the class is not default convertible.
On the other hand, integration of a Type into the lexical_cast/convert framework does require some preparation -- namely op>>() and op<<(). We could keep it for backward compatibility but might offer a new trait-based way of integrating a Type into the lexical_cast/convert framework. Like
template<> struct convert_trait<Type> { }
where we might put anything related to the integration of the Type into the convert framework. Is it too far-fetched? Thinking aloud really.
Why not?
I suppose you can reach the same effect with the error_value needed when the user want no_thow semantics.
template <typename T> struct error_value_trait { static const T value=T(); };
template <> struct error_value_trait<direction> { static const direction value=direction::down; };
So the following
direction dir = convert<direction>::from (str)(_thows=false);
will return direction::down when conversion fails after some modification on the implementation
That feels over-engineered. Somehow I feel more comfortable with more explicit
direction dir = convert<direction>::from(str, direction::dn);
You are right the failure returned value should be given at the call. What about passing it statically direction dir = convert<direction, no_throws<direction::dn> >(str); or dynamically direction dir = convert<direction>(str, (_no_throws=direction::dn));
One more question raise now. Should the fact the conversion throws or not when failing be part of the function type or a function parameter?
Do we need
direction dir = convert<direction, no_thows<> >::from (str);
or
direction dir = convert<direction>::from (str)(_thows=false);
Yes, that's a fair question. I considered convert<Type, Throw> before and back then it seemed that it was more flexible to configure the throwing behavior via a parameter.
Have you considered to return failure setting errno? How can your current interface be adapted to this use case? With a coverter template parameter you can do something like: statically direction dir = convert<direction, set_errno<EINVALID> >(str); or dynamicaly direction dir = convert<direction>(str, (_set_errno=EINVALID));
Having convert<Type>::result played a role as well.
Have you considered to convert returns just a pair<T, bool> or optional<T>? How can your current interface be adapted to these use cases? With a template parameter you can do something like pair<direction, bool> dirp = convert<direction, return_pair >(str); optional<direction> opt_dir = convert<direction, return_optional >(str); or pair<direction, bool> dirp = convert<direction>(str, _return_pair); optional<direction> opt_dir = convert<direction>(str, _return_optional);
To me, in principle, there is no much difference between supplying a parameter as a template or as an argument. So, I chose the one that looked less restrictive and scaled better. Do you feel the template-based could do better?
Templating the convert function with a converter parameter allows to have different return types that depend on the converter parameter. A model of converter is the functor class realizing the conversion using the conversion parameters template <typename To> struct converter { typedef related to To result_type; converter(); converter(converter_parameters<To> const&); template <typenam From> result_type operator()(From); }; The convert function can return the out type (To) instead of the internal converter which is implicitly convertible to the To type. template <typename To, typename Converter, typename From > Converter::retult_type convert(From f, Converter const&cv) { return cv(f); }
Resumein, I think these modifications are important, so the convert function can have always only the From parameter, and the throw semantics is preserved for non DefaultConstructible types.
Rectification, the convert function can have two parameters, first the >From type and second the converter (parameters). The converter class must define operator()(From).
From my usage pattern I do not think I want "the convert function to always have only the From parameter". In fact, truth to be told, for me it is the other way around -- I *never* use the convert function with only the From parameter. :-) I prefer having the failure value immediately visible as in the following (typical for my usage) example:
int v = convert<int>::from(s, MAX_INT); if (v == MAX_INT) conversion failed.
What about int v = convert<int>(s, _no_thows=MAX_INT); if (v == MAX_INT) conversion failed.
convert<direction>::result d = convert<direction>::from(s, direction::up); if (!d) conversion failed.
And optional<direction> d = convert<direction>(str, _return_optional ); if (!d) conversion failed.
That said, convert_trait seems tempting. What others think?
We can see _no_thows=MAX_INT _return_pair _return_optional and the other parameters as elements of a DSL configuring the conversion. This view of the convert function with a converter parameter joins a little bit the one proposed by Hartmut in a recent post. What others think? Best, Vicente