|
Boost : |
From: Ion Gaztañaga (igaztanaga_at_[hidden])
Date: 2007-08-10 12:22:31
Tobias Schwinger wrote:
> Ion Gaztañaga wrote:
> Also, please keep in mind that we are talking about a workaround,
> because decent compilers should factor out redundant machine code
> automatically.
Do you have any evidence of this? I've read somewhere that Visual 8 does
something like this, but I hope this is not the COMDAT folding option.
This redundant machine code erasing is performed by the linker, the code
is still instantiated at compile time, so we are not going to save much
compilation time.
In our example (list), all produced functions should be exactly the
same, so the linker might have it easier (I don't know if shared
libraries can also be optimized) to erase redundant functions. However,
the two level architecture (node<->value ) would still be necessary,
because functions are not exactly equal (the final transformation
between the node and the hook is not equal, unlike the core algorithm).
> Second, the current usage is quite far away from "normally specifying
> template arguments", isn't it?
Well, for an average C++ programmer, a template parameters list
template <class ValueTraits, bool ConstantTimeSize, ...>
is surely easier to understand that seeing something like
template<class Policy1 = no_policy, class Policy2 = no_policy, >
and having to write
class_name
<class T
,policy_name_a<parameter_to_policy2>,
,policy_name_a<parameter_to_policy3>...
>
specially when we only have 3 template parameters.
I don't consider myself a novice, but I still don't easily grok this
style (surely because no standard C++ utility uses this approach and I'm
not used to it).
Anyway the derivation approach of your attached code should minimize
instantiation problems. All the instantiated code is the same for all
policy combinations, and only the front-end class is different.
Another question is if having different C++ types for otherwise
semantically exact concepts (just that policies have been specified in
another order) is a problem. Even if it's not a problem, that makes me
really nervous. That's why I think something like:
typedef options
< void_pointer<void*>
, constant_time_size<false>
>::type my_options;
list<T, my_options>
is more straightforward and understandable, since the declaration of the
class is something like:
template<class T, class Options = default_list_options>
class list;
Any C++ programmer would instantly understand how to use the class ("Oh,
a container of type T, where I can tweak some options"). I know, I'm
from the old school, but you know: "One concept, one type" ;-)
The specification of different hook types (base, member or maybe
non-intrusive hooks) might not be homogeneous (the base hook needs a
tag, the member hook needs a pointer to member, the non-intrusive hook
might need... everything the user want to put in it). Also, taking out
the T type might cause current hook definition cry.
Anyway, I need to think about this more carefully, because the interface
change *might* lead to a considerable architecture redesign (not sure it
would provoke it, but I'm still trying to grok the consequences) .
>
> So it comes down to whether we prefer to write e.g.
>
> (variant 1)
>
> container< type::value_traits<type>, a_default, another_default,
> false >
>
> or (variant 2)
>
> struct my_settings : defaults
> {
> static bool const constant_time_size_operation = false;
> };
>
> container< type, my_settings >
>
> or (variant 3)
>
> container< type, constant_time_size_operation<false> >
>
>
> Is it easier to write customize a container with variant 1, 2 or 3?
Depends on the programmer. The fact is that variant1 has been in use for
years and programmers are used to it. The programmer that wants to know
what a_default means will jump to the declaration (if using a IDE it
will show you the definition automatically). Of course, code is more
self-documented with options 2 and 3.
> Which one is the easiest to understand (ideally without reading through
> the docs)?
Without reading the docs? Maybe letting the code non-understandable has
its advantages.. (just kidding)
> So variant 1 is certainly the least expressive: "What the heck does that
> 'false' say?" and the defaults might not be identifiable as such and
> cause similar questions.
Ok. Forget the "false" value, I know it's a bad choice, I should have
defined a type called:
enum SizeComplexity { ConstantTimeSize, LinearTimeSize };
template
< class ValueTraits
, SizeComplexity = ConstantTimeSize
, class SizeType = std::size_t>
class list;
> Variant 2 is self-explanatory but too verbose, thus probably making the
> customization of the container seem more important than it actually is -
> the constant time size operation might got disabled because it's not
> needed - just a nuance for optimization.
>
> Variant 3 doesn't have any of these drawbacks.
Variant 2 can be very similar to variant 3 (we don't need an external
options type and derive from default options). Just do the argument
processing outside the class so that we don't complicate the class with
argument processing:
typedef
container
< T
, list_options
< constant_time_size_operation<false>
, void_pointer_type<offset_ptr<void>
,... // Unspecified options take default values
>
>::type >
MyContainer;
where container is:
template<class T, class ListOptions = list_options<> >
class container;
Container is exactly the same type for the same options and my
nervousness disappears ;-)
Regards,
Ion
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk