
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