Design of a compatible policy-based template

The main problem with a policy-based type, such as a string type with many options (flex_string for example), is that different versions of the template are different types, and as such are incompatible. An obvious solution would be a type that can hold any of the template instantiations, and dispatch the member functions to the appropriate type through virtual calls, that could eventually use the stack like boost.variant. However, this should have quite of a performance penalty, and some of the advantages of the policies (such as Small String Optimization) would become either useless (dynamic allocation) or annoying when disabled (stack allocation). An alternative would be to simply use typedef to choose the type you want to use throughout the program, which would somehow give source compatibility. How do people feel about types with lots of policies? How could those issues be addressed, for example in the case of a string type, where there is quite some demand for policies?

The main problem with a policy-based type, such as a string type with many options (flex_string for example), is that different versions of the template are different types, and as such are incompatible.
This is a significant issue. A good illustration of the problem is the Allocator template parameter (call it a policy if you want) in standard containers. If I want to use custom allocator in my std::vector as part of a library interface, all user code is required to be templatized. The increase in physical coupling is not justified.
An obvious solution would be a type that can hold any of the template instantiations, and dispatch the member functions to the appropriate type through virtual calls, that could eventually use the stack like boost.variant.
Assuming that the additional flexibility you get with policy-based design is needed -- yes, that would be a solution. Often, a better approach is to design less flexible interface that better suits the problem domain. I compare policy-based design to a swiss army knife. If you really have o -- you don't have a proper knife, or scissors, or whatever -- indeed you *can* use it for all kinds of things.
An alternative would be to simply use typedef to choose the type you want to use throughout the program, which would somehow give source compatibility.
As far as users are concerned, using a typedef is exactly the same as using distinct types instead of a single policy-based template, except that a policy-based design would be more restrictive (for the library designer). Emil Dotchevski

Mathias Gaunard wrote:
How do people feel about types with lots of policies?
To my mind, a policy should be extracted to the template parameter only when its difference makes the instantiated types not compatible with each other. From this point of view the allocators should not be present in template parameters of strings or containers, since they don't alter the main function of these components. On the other hand, trying to support these policies in runtime will always give a performance overhead. Whether it is significant is a case-by-case decision, but in most times it is not. In return you get a more constrained and stable set of types and runtime flexibility. I tend to think it overweights the drawbacks.
How could those issues be addressed, for example in the case of a string type, where there is quite some demand for policies?
If such policies are already present in the template parameters list, I try to provide templated operations and functions that may work regardless from the policies you are not interested in. This may lead to some code bloat, so I often try to go down to some fundamental types for internal processings. For example: // This is actual foo implementation. It may be defined in some // cpp and explicitly instantiated on a relatively short list of types. template< typename CharT > void foo_internal( const CharT* begin1, const CharT* end1, const CharT* begin2, const CharT* end2); template< typename CharT, typename TraitsT1, typename TraitsT2, typename AllocT1, typename AllocT2
void foo( std::basic_string< CharT, TraitsT1, AllocT1 > const& s1, std::basic_string< CharT, TraitsT2, AllocT2 > const& s2) { foo_internal(&*s1.begin(), &*s1.end(), &*s2.begin(), &*s2.end()); } If the case is that you are developing a new component, the solution similar to shared_ptr is a good one. Especially, if you are able to get rid of the dynamic allocation (which I, personally, paranoidly avoid :) ).
participants (3)
-
Andrey Semashev
-
Emil Dotchevski
-
Mathias Gaunard