Boost logo

Boost :

Subject: Re: [boost] [convert] are you mixing default_value and error_value?
From: Gottlob Frege (gottlobfrege_at_[hidden])
Date: 2009-07-07 01:09:25


On Mon, Jul 6, 2009 at 6:03 PM, Vladimir
Batov<vladimir.batov_at_[hidden]> wrote:
>> 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.
>
> 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.
>

default and error are *mostly* orthogonal
default and DefaultConstructible are *mostly* orthogonal as well:

default and error:
- if I supply a default, then I do NOT expect an error
- if we did (as once suggested)
      int x = convert<int>::from(str, 0, &error);
  then I supply a default so that x is set to something, but still
want any error (warning?) info

default and DefaultConstructible:
- if the To type is NOT DefaultConstructible, then if I want a
default, I need to supply one
- if the To type IS DefaultConstructible, then I might STILL want to
pass in a default:
     int min = convert<int>::from(minStr, 0); // get min or assume 0
     int max = convert<int>::from(maxStr, 100); // get max, else 100

so for a given To type:

if (To is DefaultConstructible)
{
    if (specific default requested)
    {
        int max = convert<int>::from(str, 100);
        // default supplied so no error
    }
    else
    {
         if (want the "class-defined" default)
         {
              // ie we want convert to default-construct the result if necessary

              int max = convert<int>::from(str, throw_ = false);
              // or ?
              int max = convert<int>::from(str); // ie what's the
default for throw_?
              // or, of course
              int max = convert<int>::from(str, int());
         }
         else // want errors
         {
              int m = convert<int>::from(str, throw_ = true);
              // or maybe
              int m = convert<int>::from(str, &error);
         }
    }
}
else // NOT DefaultConstructible
{
   struct NoDefCon
   {
       NoDefCon(int x, int y) { }
   };

   if (want specific default)
   {
       // supply default explicitly
       // same form as other specific-default case above
       NoDefCon ndc = convert<NoDefCon>::from(str, NoDefCon(1, 3));
   }
   else
   {
       // don't want a specific default?
       // no choice but to throw:
       NoDefCon ndc = convert<NoDefCon>::from(str);
   }
}

If all the concepts were completely orthogonal, we'd have a few more if blocks.

>> 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. 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.
>
>> 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);
>
>> 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. Having convert<Type>::result played a role as well.
>
> 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?
>
>> 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.
>
> >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.
>
> convert<direction>::result d = convert<direction>::from(s, direction::up);
> if (!d) conversion failed.
>
> That said, convert_trait seems tempting. What others think?
>
> V.
>

Overall, I think defaults are not class-level decisions, but
call-level choices. Like the min/max case. One convert call might
use 0 as the default, the other 100.

I DO think that an error_value, IS (typically) a class-level trait.
Well, maybe not for ints. Sometimes, in one context, 0 might be a
good error value, sometimes -1, etc. Often it may be the case that
need a stronger type than 'int', but that doesn't always happen in
pratice. Could always pass in the trait:

   int x = convert<int, my_convert_traits>::from(str);

with a default of convert_trait<To> if not specified.

Basically, I'm not convinced about the traits...

Tony


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk