[Dune] ParameterTree

Carsten Gräser graeser at math.fu-berlin.de
Sun Jan 30 23:04:28 CET 2011


Am 30.01.2011 22:22, schrieb Jö Fahlke:
> Am Fri, 28. Jan 2011, 15:02:10 +0100 schrieb Carsten Gräser:
>> If you use
>>
>>   T t = pTree.get("key", expression);
>>
>> what you really want will almost always be the string->T conversion.
>> Instead of this the string->typeof(expression) conversion is used.
>> For example the user might expect
>>
>>   double x = pTree.get("key", 0);
>>
>> to parse the value as double which is not done. In this case it's
>> safe because you get an exception. In others it need not to be safe.
> 
> OK, I think I understand your argument better now.  But by the same argument
> you would have to disallow the "/"-operator and force everyone to use
> something like
> 
>   template<class T> struct Forward { typedef T type; };
>   template<class T> T div(typename Forward<T>::type nominator,
>                           typename Forward<T>::type denominator);
> 
> because
> 
>   ctype h = 1/elements_per_dimension;
> 
> is not doing what people expect.
> 
> I know there are a lot of places where I did choose the overload by the type
> of the default value; I would have to change all of them, and I'm sure I'm not
> the only one.  This is a C-derived language and we can expect programmers to
> be aware of this kind of trap, so I don't think it is worth the effort.
> 
>>>>> methods were sometimes implemented as overloads made this the only reliable
>>>>> way.  I consider The fact that most of these overloads are provided by a
>>>>> method template an implementation detail.
>>>> In my opinion it provides a natural way to select a type: using a template parameter.
>>>>
>>>>> Instead I propose specialize the ParameterTree::Parser struct for char to read
>>>>> a numerical value instead of a character.
>>>> You might also want the character depending on your application.
>>>> The main reason to forbid the implicit type determination is that
>>>> similar unexpected behaviour might appear if the user defines the
>>>> conversion for custom types.
>>>
>>> If you want the character, you can always read a std::string, check that it's
>>> size exactly 1 and take it's first character.  If we keep it as it is, and you
>>> want the numerical value, you can convert the default value to an int and
>>> convert the return value back to a char, while checking that the return value
>>> actually fits in a char.  Either way you make it a litte harder for some
>>> poeple, and I don't care much which way we choose.  I believe that it is a
>>> little more likely that people want the numerical value for a char (I've run
>>> into the same problem before...)
>>>
>>> As to custom conversions, do you mean IO-conversions (operator<<) or type
>>> conversions here?  In both cases I don't see a problem that could be solved by
>>> specifying the conversion type explicitly.
>>
>> Currently you can simply specialize Parser<A> and Parser<B> such that
>> the constructer A(B) exists and is commenly used. If you don't have
>>
>>   Parser<A>::parse(string)  == (A)(Parser<B>::parse(string))
>>
>> the following will not do the same
>>
>>   B b;
>>   A a1 = pTree.get("key", b);
>>   A a1 = pTree.get<A>("key", b);
>>
>> while the later is what you want in most cases.
> 
> Nor would I expect them to do the same, necessarily.  In fact I wouldn't even
> expect those two to do the same:
> 
>   A a;
>   A a1 = pTree.get("key", a);
>   A a2 = pTree.get<A>("key", a);
> 
> On the other hand, I don't see how the existance of the specializations
> 
>   template<> class Parser<A>;
>   template<> class Parser<B>;
> 
> can influence the existance of a constructor of A that takes B as an argument,
> or the existance of any other conversion from A to B, for that matter, so I
> may have missed your point here.
> 
> Maybe this is about the following: If you have a template function and an
> exact overload
> 
>   template<class T> T get(const std::string &name, const T &defaultValue);
>   double get(const std::string &name, double defaultValue);
> 
> then for
> 
>   get("key", float(0));
> 
> the function template is selected, while you would expect the non-template
> function to be selected?
> 
>>                                                 One example would be the following:
>> Suppose you have specialized Parser<R> for some type R for exact rational numbers
>> that can e.g. be constructed from double. Then
>>
>>   R r = pTree.get<R>("key", 0.0):
>>
>> will result in r==1/10 while
>>
>>   R r = pTree.get("key", 0.0);
>>
>> will result in r!=1/10.
> 
> I would write that as
> 
>   R r = pTree.get("key", R(0.0));
> 
>>                         Further examples are
>>
>>  std::vector<int> v;
>>
>>  long long ll = pTree.get("key", v.size());
> 
>   long long ll = pTree.get("key", (long long)(v.size()));
> 
>>  std::cout << ll << std::endl;
>>
>>  std::cout << pTree.get("key", v.size()) << std::endl;
> 
> I'm not sure which overload you mean here.
> 
>>  int i        = pTree.get("key", v.size());
> 
>   int i        = pTree.get("key", int(v.size()));
> 
>>  std::cout << i << std::endl;
>>
>> Guess what happens for key=-1. Only the last one prints -1, funny, isn't it?
> 
> Well, and
> 
>   std::cout << 1/3 << std::endl;
> 
> will print 0.  And actually, your calls to get() should throw exceptions
> because "-1" cannot be parsed as an unsigned value of type std::size_t.
> 
> 
> You are right that you have the make sure that the type of the defaultValue is
> the one you actually want to use for the parsing.  If it isn't, you have to
> explicitly convert it.  On the other hand, if that value is alreay of the
> correct type, there is not need to specify the type again.  For instance, I
> like to collect my default values somewhere at the top of my programs or
> another designated place
> 
>   // default values
>   unsigned global_refines = 0;
> 
> and later I overwrite them with a value read from configuration
> 
>   int main() {
>     // ...
>     global_refines = params.get("global_refines", global_refines);
>     // ...
>   }
> 
> If you have your way, I would have to explicitly write params.get<unsigned>().
> Should I later introduce a special meaning for global_refines==-1, I would
> have to change its type to int.  But then it is easy to forget to change the
> type in the call to get() too, so there are some pitfalls here as well.

I know that the implicit type selection is nice. Furthermore it's clear
how to select the correct type manually. The proposed change would just
help to avoid hard to find errors. And since ParameterTree is not released
yet it would be the right time to do the change. If you don't want the
write type explicitly you could still use

template<class T, class T2>
void get(const ParameterTree& p, const std::string& key, T& value, const T2& defaultValue)
{
  value = p.get<T>(key, defaultValue);
}

This would also avoid the pitfall.

However, if I'm the only one that see's a problem here I withdraw my proposal.

Best,
Carsten

-- 
----------------------------------------------------------------------
Carsten Gräser           | phone: +49-30 / 838-75349
Freie Universität Berlin | fax  : +49-30 / 838-54977
Institut für Mathematik  | email: graeser at math.fu-berlin.de
Arnimallee 6             |
14195 Berlin, Germany    | URL  : http://page.mi.fu-berlin.de/graeser
----------------------------------------------------------------------




More information about the Dune mailing list