Hard to come up with a good title for this question. What I really need is to be able to provide template parameters with different number of arguments in place of a single parameter. Doesn't make a lot of sense so I'll go over the reason:
template < typename T, template <typename,typename> class Policy = default_policy >
struct policy_based : Policy<T, policy_based<T,Policy> >
{
// inherits R Policy::fun(arg0, arg1, arg2,...,argn)
};
// normal use:
policy_base<type_a> instance;
// abnormal use:
template < typename PolicyBased > // No T since T is always the same when you use this
struct custom_policy {};
policy_base<type_b,custom_policy> instance;
The deal is that for many abnormal uses the Policy will be based on one single type T, and can't really be parameterized on T so it makes no sense to take T as a parameter. For other uses, including the default, a Policy can make sense with any T.
I have a couple ideas but none of them are really favorites. I thought that I had a better answer--using composition instead of policies--but then I realized I have this case where fun() actually needs extra information that the class itself won't have.
This is like the third time I've refactored this silly construct and I've got quite a few custom versions of it around that I'm trying to consolidate. I'd like to get something nailed down this time rather than just fish around and hope it works this time. So I'm just fishing for ideas right now hoping that someone has something I'll be so impressed by that I'll switch deities. Anyone have a good idea?
Edit: You might be asking yourself why I don't just retrieve T from the definition of policy based in the template for default_policy. The reason is that default_policy is actually specialized for some types T. Since asking the question I have come up with something that may be what I need, which will follow, but I could still use some other ideas.
template < typename T >
struct default_policy;
template < typename T, template < typename > class Policy = default_policy >
struct test : Policy<test<T,Policy>>
{};
template < typename T >
struct default_policy< test<T, default_policy> >
{
void f() {}
};
template < >
struct default_policy< test<int, default_policy> >
{
void f(int) {}
};
Edit:
Still messing with it. I wasn't too fond of the above since it makes default_policy permanently coupled with "test" and so couldn't be reused in some other method, such as with multiple templates as suggested below. It also doesn't scale at all and requires a list of parameters at least as long as "test" has. Tried a few different approaches that failed until I found another that seems to work so far:
template < typename T >
struct default_policy;
template < typename T, template < typename > class Policy = default_policy >
struct test : Policy<test<T,Policy>>
{};
template < typename PolicyBased >
struct fetch_t;
template < typename PolicyBased, typename T > struct default_policy_base;
template < typename PolicyBased >
struct default_policy : default_policy_base<PolicyBased, typename fetch_t<PolicyBased>::type> {};
template < typename T, template < typename > class Policy >
struct fetch_t< test<T,Policy> > { typedef T type; };
template < typename PolicyBased, typename T >
struct default_policy_base
{
void f() {}
};
template < typename PolicyBased >
struct default_policy_base<PolicyBased,int>
{
void f(int) {}
};
I had a similar problem, and did not find a desirable answer. From what I know, C++ doesn't have elegant support for variable amounts of template arguments, so you have to get around it by "wrapping" extra arguments in another class;
policy_base< twoArgs<part1, part2> >
You can pretty this up a little bit by putting making typedefs for twoArgs, but not by a lot in this basic case. (Remember, you can "do" templated typedefs by using member typedefs in templated classes.)
or by having many different declarations of the core template, with different args;
template< typename T1 > struct base {...}
template< typename T1, typename t2 > struct base {...}
//etc
Have you looked at so-called Named Template Parameters? This allows you to have many parameters with a hidden default, each of which can be overridden by name (i.e. in arbitrary order). It's a trick relying on multiple levels of indirection and multiple inheritance. It's described in chapter 16.1 of the book "Templates the Complete Guide" by Vandevoorde & Josuttis. For an online exposition see here. The idea is implemented in Boost.Parameter
Here's a short summary for the Boost.Parameter library.
Step 1) you declare each parameter par0 through parN through the following macros:
BOOST_PARAMETER_TEMPLATE_KEYWORD(par0)
BOOST_PARAMETER_TEMPLATE_KEYWORD(par1)
// ...
BOOST_PARAMETER_TEMPLATE_KEYWORD(parN)
Each macro will define both a regular class par0 in namespace tag and a class template par0 at namespace scope
namespace tag { struct par0; } // keyword tag type
template <class T>
struct par0
:
parameter::template_keyword<tag::par0, T>
{};
Step 2) you declare your class signature with the required and optional parameters:
using boost::mpl::_;
typedef parameter::parameters<
parameter::required<tag::par0>
, parameter::optional<tag::par1>
// ...
, parameter::optional<tag::parN>
> your_signature;
Step 3) you declare your policy class
template <
class a0
, class a1 = parameter::void_
// ...
, class aN = parameter::void_
>
struct your_policy
{
// Create ArgumentPack
typedef typename
your_signature::bind<a0, a1, /* ... */ ,aN>::type
args;
typedef typename parameter::value_type<
args, tag::par0>::type par0;
typedef typename parameter::value_type<
args, tag::par1, your_par1_default>::type par1;
// ...
typedef typename parameter::value_type<
args, tag::parN, your_parN_default>::type parN;
};
Step 4) define your policies and specializations
template<typename T>
class default_policy
:
your_policy<T> // all hidden template parameters equal to their defaults
{};
template<typename T>
class some_par2_specialized_policy
:
your_policy<T, par2<some_override_for_par2> > // note par1 does not have to be overriden!!
{};
For a flexible and scalable policy-based design, my experience is that Boost.Parameter works like a charm (you might need to override BOOST_PARAMETER_MAX_ARITY if you have more than 8 hidden parameters though).
It should be possible to adapt your design to this technique. Of course, once you introduce extra template parameters that have hidden defaults, you need to pick off the cases where they are overridden the code that uses your policy class. This is no different than when you use multiple traits to communicate your specialized behavior to client code, except that with policies you have everything nicely wrapped together.
Related
Say I have a template class like so:
template < typename TParam >
class Test
{
// content
};
I want to pull out the first template parameter of TParam if it's a specialization of a class template. Something like:
template < typename TParam >
class Test
{
using TParamInner = TemplateType<TParam>::Type;
// use TParamInner here
};
Additional info:
I have access to all of C++98.
I have access to a subset of C++11.
I would prefer to avoid the stdlib if possible (assume this is
because I'm using an embedded system for which no stdlib is available and/or because I am heavily memory-constrained)
You can get close with something like:
template <class >
struct first_template_param;
template <template <class...> class Z, class T, class... Ts>
struct first_template_param<Z<T, Ts...>> {
using type = T;
}
It won't handle std::array or any other class templates that take non-type template parameters. But it'll handle all the "normal" class templates. You can always then add extra specializations for all the ones you want:
template <class T, size_t N>
struct first_template_param<std::array<T,N>> {
using type = T;
}
Thanks to #Barry for spurring the solution along.
It's not a complete answer for all template types, but it works for templates where all parameters are types, which is a large number of the most useful templates.
template < typename Head, typename ... Tail >
struct split { using first = Head; };
template <class >
struct cls_template_info; // fails on non-templates
template <template <class...> class Z, class... Ts>
struct cls_template_info<Z<Ts...>>
{
using type = typename split<Ts...>::first; // typename used to disambiguate
};
This can then be used as using T = cls_template_info<std::vector<int>>::first;.
You can't. A template type is never carried up to runtime. You have to instantiate it (this leads to a complete new type), and the compiler then generates the needed code to make it appear as if you have defined specifically for the type parameters you specified. Indeed, in old compilers (this has been solved a lot of time ago) when you instantiate a generic type in several compilation units, that lead to several repetitions of the same code in the final program. But as I've said, this has been solved time ago.
I wrote a simple class for the moving average which can be used with an AVR.
template<typename T, typename Tsum = int32_t>
class MovingAverage { ... }
But now I want to specialize this class for float without copying and pasting the whole class body and change all T and Tsum to float and that I do not need to use two template parameters. Tsum is the type for the 'sum' variable where all passed values of type T were summed up. If T is 'uint8_t' it is a good idea to use 'uint32_t' for the sum, but for float or double there is no need to use a datatype with higher precision, so I want only one parameter for this purpose. I thought it could work this way:
typedef MovingAverage<float, float> MovingAverage<float>
or this way:
template<>
class MovingAverage<float> : public MovingAverage<float, float> {};
But I was wrong and I found only solutions where I have to write my code twice.
Is there a way to write the class only once and then specialize it this way I prefer?
Thanks in advance!
If you want different default types for Tsum, this should be outsourced to another class which can be specified, for example:
template< typename, typename = void >
struct DefaultSum { using type = int32_t; };
template< typename T >
struct DefaultSum< T, typename std::enable_if<
std::is_floating_point< T >::value
>::type >
{ using type = T; };
template<typename T, typename Tsum = typename DefaultSum<T>::type >
class MovingAverage { ... }
You could write a simple traits class
// general version
template<typename T>
struct sum_type
{
typedef int32_t type;
};
// specialized version
template<>
struct sum_type<float>
{
typedef float type;
};
// repeat for double, the solution from #DanielFrey is even more sophisticated
// as it specializes all floating point types in one sweep.
and then extract this type in your class template
template<typename T, typename Tsum = typename sum_type<T>::type>
// ^^^^^^^^ <-- dependent type disambiguation
class MovingAverage { ... };
Note that this only works if your MovingAverage has a regularly parameterized implementation. If you are actually doing something special for float (e.g. rewrite expressions to take care of the non-associative character of floating point arithmetic), then you need to do more work.
If you are serious about working with C++ templates, run -not walk- to the nearest bookstore and get the book C++ Templates: The Complete Guide. Section 15.1 has a 15+ page discussion of defining a generic accumulate class template.
If I have the following template parameters:
template <typename T_Key, typename T_Value, typename T_HashFunc,
typename T_ExtractKey, typename T_EqualKey, typename T_RehashClass, typename T_HashcodeClass>
class Hashtable { /* ... */ };
Where T_RehashClass and T_HashcodeClass are two template classes that take in T_Key, T_Value, T_HashFunc, T_ExtractKey and T_EqualKey as well. I would like those classes to grab the types from Hashtable's typedef list (all template parameter types in Hashtable are typedefined).
Note that T_RehashClass and T_HashcodeClass can be created by the user as well (defaults are provided) and can have other template parameters if the user wishes.
As it stands, any class that is a T_RehashClass must have T_Key, T_Value etc. template parameters filled out, which I see as code duplication. I would like the class to somehow have knowledge of Hashtable so that it can access its typedefs and deduce T_Key, T_Value etc. types automatically by creating its own typedefs. Unfortunately, in this case, I get a cyclic dependency.
How is this type of problem solved generally?
Also note that I am following EASTL where EASTL uses multiple inheritance to inherit from T_RehashClass and T_HashnodeClass instead and consequently, Hashtable has even more template parameters. I wondered if there was a way around it (i.e. not inheriting from both policies and have them as template parameters as inheriting from the policies reduces flexibility).
One solution that I thought of was to have a template struct that has all the template parameters from T_Key to T_EqualKey. The Hashtable declaration will then be:
template <typename T_HashtableParams, typename T_RehashClass = default_rehash_class<T_HashtableParams>, typename T_HashcodeClass = default_hashnode_class<T_HashtableParams> >
class Hashtable { /* ... */ };
The T_RehashClass and T_HashcodeClass can then take the defaults which will eliminate code duplication. Problem with this approach is that it is more cumbersome for the user to use.
I'm not sure it's terribly interesting to specify different T_Key and T_Value types for your hash and rehash classes. If I were going about this problem, I would try and set up a policy for the key/val first. My inclination is to say that there maybe should be a ValuePolicy instead of grouping it with KeyPolicy, but that is neither here nor there.
namespace hash {
namespace detail {
template<
typename Key
, typename Value
, typename KeyGetter
, typename KeyComparator>
class KeyPolicy {
public:
typedef Key key_t;
typedef Value value_t;
typedef KeyGetter get_t;
typedef KeyComparator compare_t;
};
}} // hash detail
The HashTable isn't valid unless it has the same KeyPolicy as the rehast, etc so don't give it one.
namespace hash {
namespace detail {
template<typename RehashPolicy>
class HashTableImpl {
public:
typedef RehashPolicy rehash_p;
typedef typename rehash_p::key_policy_t::key_t key_t;
// typedef ...
};
// this doesn't have a specific name, its anything.
template<typename KeyPolicy>
class SomeRehashPolicy {
public:
typedef KeyPolicy key_policy_t;
};
}} // hash detail
You can add whatever typedefs you want there, obviously. If I were a stickler in a code review I'd probably ask for things like rehash_p and key_policy_t to be private. They are implementation details, really. The actual invariant you are trying to protect are what are in key_t, etc.
Maybe I'm outside the reasonable bounds of etiquette, but my honest opinion is that all this configuration is only interesting to the guy that wrote it. Not you, not anyone using it. So I would only expose the HashTable configuration or two that people are actually going to use.
namespace hash {
struct stub {}; // sorry, I'm lazy
template<typename Key, typename Value>
class HashTable {
private:
typedef typename detail::KeyPolicy<Key, Value, stub, stub> key_p;
typedef typename detail::SomeRehashPolicy<key_p> rehash_p;
typedef typename detail::HashTableImpl<rehash_p> impl_t;
public:
typedef typename impl_t::key_t key_t;
};
} // hash
int main(int argc, char ** argv) {
hash::HashTable<int, double> hash_table;
return 0;
}
Lots of details aren't filled in obviously, but you get the idea.
See e.g. Modern C++ Design (sec. 1.5.1: Implementing Policies with template-template parameters). The idea is to let T_RehashClass and T_HashcodeClass be template-template parameters. These classes take the T_Key, T_Value and whatever else as their own parameters. To avoid retyping, you can then inherit the needed instantiation of those templates.
template <
typename T_Key,
typename T_Value,
typename T_HashFunc,
typename T_ExtractKey,
typename T_EqualKey,
template<typename, typename /*, more params */> class T_RehashClass,
template<typename, typename /*, more params */> class T_HashcodeClass
> class Hashtable
:
public T_RehashClass<T_Key, T_Value /*, more params */>,
public T_HashcodeClass<T_Key, T_Value /*, more params */>
{ /* ... */ };
NOTE: you really need "class" and not "typename" in front of T_RehashClass and T_HashcodeClass because they are template names, not template types!
Assume I have a template (called ExampleTemplate) that takes two arguments: a container type (e.g. list, vector) and a contained type (e.g. float, bool, etc). Since containers are in fact templates, this template has a template param. This is what I had to write:
#include <vector>
#include <list>
using namespace std;
template < template <class,class> class C, typename T>
class ExampleTemplate {
C<T,allocator<T> > items;
public:
....
};
main()
{
ExampleTemplate<list,int> a;
ExampleTemplate<vector,float> b;
}
You may ask what is the "allocator" thing about. Well, Initially, I tried the obvious thing...
template < template <class> class C, typename T>
class ExampleTemplate {
C<T> items;
};
...but I unfortunately found out that the default argument of the allocator...
vector<T, Alloc>
list<T, Alloc>
etc
...had to be explicitely "reserved" in the template declaration.
This, as you can see, makes the code uglier, and forces me to reproduce the default values of the template arguments (in this case, the allocator).
Which is BAD.
EDIT: The question is not about the specific problem of containers - it is about "Default values in templates with template arguments", and the above is just an example. Answers depending on the knowledge that STL containers have a "::value_type" are not what I am after. Think of the generic problem: if I need to use a template argument C in a template ExampleTemplate, then in the body of ExampleTemplate, do I have to reproduce the default arguments of C when I use it? If I have to, doesn't that introduce unnecessary repetition and other problems (in this case, where C is an STL container, portability issues - e.g. "allocator" )?
Perhaps you'd prefer this:
#include <vector>
#include <list>
using namespace std;
template <class Container>
class ForExamplePurposes {
typedef typename Container::value_type T;
Container items;
public:
};
int main()
{
ForExamplePurposes< list<int> > a;
ForExamplePurposes< vector<float> > b;
}
This uses "static duck typing". It is also a bit more flexible as it doesn't force the Container type to support STL's Allocator concept.
Perhaps using the type traits idiom can give you a way out:
#include <vector>
#include <list>
using namespace std;
struct MyFunkyContainer
{
typedef int funky_type;
// ... rest of custom container declaration
};
// General case assumes STL-compatible container
template <class Container>
struct ValueTypeOf
{
typedef typename Container::value_type type;
};
// Specialization for MyFunkyContainer
template <>
struct ValueTypeOf<MyFunkyContainer>
{
typedef MyFunkyContainer::funky_type type;
};
template <class Container>
class ForExamplePurposes {
typedef typename ValueTypeOf<Container>::type T;
Container items;
public:
};
int main()
{
ForExamplePurposes< list<int> > a;
ForExamplePurposes< vector<float> > b;
ForExamplePurposes< MyFunkyContainer > c;
}
Someone who wants to use ForExamplePurposes with a non-STL-compliant container would need to specialize the ValueTypeOf traits class.
I would propose to create adapters.
Your class should be created with the exact level of personalization that is required by the class:
template <template <class> C, template T>
class Example
{
typedef T Type;
typedef C<T> Container;
};
EDIT: attempting to provide more is nice, but doomed to fail, look at the various expansions:
std::vector<T>: std::vector<T, std::allocator<T>>
std::stack<T>: std::stack<T, std::deque<T>>
std::set<T>: std::set<T, std::less<T>, std::allocator<T>>
The second is an adapter, and so does not take an allocator, and the third does not have the same arity. You need therefore to put the onus on the user.
If a user wishes to use it with a type that does not respect the expressed arity, then the simplest way for him is to provide (locally) an adapter:
template <typename T>
using Vector = std::vector<T>; // C++0x
Example<Vector, bool> example;
I am wondering about the use of parameter packs (variadic templates) here... I don't know if declaring C as template <class...> C would do the trick or if the compiler would require a variadic class then.
You have to give the full template signature, including default parameters, if you want to be able to use the template template parameter the usual way.
template <typename T, template <class U, class V = allocator<U> > class C>
class ExampleTemplate {
C<T> items;
public:
....
};
If you want to handle other containers that the one from the STL, you can delegate container construction to a helper.
// Other specialization failed. Instantiate a std::vector.
template <typename T, typename C>
struct make_container_
{
typedef std::vector<T> result;
};
// STL containers
template <typename T, template <class U, class V = allocator<U> > class C>
struct make_container_<T,C>
{
typedef C<T> result;
};
// Other specializations
...
template <typename T, typename C>
class ExampleTemplate {
make_container_<T,C>::result items;
public:
....
};
I think, it is required to reproduce all template parameters, even default. Note, that Standard itself does not use template template parameters for containter adaptors, and prefers to use regular template parameters:
template < class T , class Container = deque <T > > class queue { ... };
template < class T , class Container = vector <T>, class Compare = less < typename Container :: value_type > > class priority_queue { ... };
The following code will allow you to do something like you're asking for. Of course, this won't work with standard containers, since this has to already be part of the template class that's being passed into the template.
/* Allows you to create template classes that allow users to specify only some
* of the default parameters, and some not.
*
* Example:
* template <typename A = use_default, typename B = use_default>
* class foo
* {
* typedef use_default_param<A, int> a_type;
* typedef use_default_param<B, double> b_type;
* ...
* };
*
* foo<use_default, bool> x;
* foo<char, use_default> y;
*/
struct use_default;
template<class param, class default_type>
struct default_param
{
typedef param type;
};
template<class default_type>
struct default_param<use_default, default_type>
{
typedef default_type type;
};
But I don't really think this is what you're looking for. What you're doing with the containers is unlikely to be applicable to arbitrary containers as many of them will have the problem you're having with multiple default parameters with non-obvious types as defaults.
As the question exactly described the problem I had in my code (--I'm using Visual Studio 2015), I figured out an alternative solution which I wanted to share.
The idea is the following: instead of passing a template template parameter to the ExampleTemplate class template, one can also pass a normal typename which contains a type DummyType as dummy parameter, say std::vector<DummyType>.
Then, inside the class, one replace this dummy parameter by something reasonable. For replacement of the typethe following helper classes can be used:
// this is simply the replacement for a normal type:
// it takes a type T, and possibly replaces it with ReplaceByType
template<typename T, typename ReplaceWhatType, typename ReplaceByType>
struct replace_type
{
using type = std::conditional_t<std::is_same<T, ReplaceWhatType>::value, ReplaceByType, T>;
};
// this sets up the recursion, such that replacement also happens
// in contained nested types
// example: in "std::vector<T, allocator<T> >", both T's are replaced
template<template<typename ...> class C, typename ... Args, typename ReplaceWhatType, typename ReplaceByType>
struct replace_type<C<Args ...>, ReplaceWhatType, ReplaceByType>
{
using type = C<typename replace_type<Args, ReplaceWhatType, ReplaceByType>::type ...>;
};
// an alias for convenience
template<typename ... Args>
using replace_type_t = typename replace_type<Args ...>::type;
Note the recursive step in replace_type, which takes care that types nested in other classes are replaced as well -- with this, for example, in std::vector<T, allocator<T> >, both T's are replaced and not only the first one. The same goes for more than one nesting hierarchy.
Next, you can use this in your ExampleTemplate-class,
struct DummyType {};
template <typename C, typename T>
struct ExampleTemplate
{
replace_type_t<C, DummyType, T> items;
};
and call it via
int main()
{
ExampleTemplate<std::vector<DummyType>, float> a;
a.items.push_back(1.0);
//a.items.push_back("Hello"); // prints an error message which shows that DummyType is replaced correctly
ExampleTemplate<std::list<DummyType>, float> b;
b.items.push_back(1.0);
//b.items.push_back("Hello"); // prints an error message which shows that DummyType is replaced correctly
ExampleTemplate<std::map<int, DummyType>, float> c;
c.items[0]=1.0;
//c.items[0]="Hello"; // prints an error message which shows that DummyType is replaced correctly
}
DEMO
Beside the not-that-nice syntac, this has the advantage that
It works with any number of default template parameters -- for instance, consider the case with std::map in the example.
There is no need to explicitly specify any default template parameters whatsoever.
It can be easily extended to more dummy parameters (whereas then it probably should not be called by users ...).
By the way: Instead of the dummy type you can also use the std::placeholder's ... just realized that it might be a bit nicer.
I have a set of classes in a logging framework used by project A and B. I am refactoring the framework so it can be used in project B and C. The refactoring mainly consists of giving everything template parameters: project A might run on an embedded device with poor/no STL implemenatation, while B and C just run on a pc, but B is single threaded while C uses multithreading.
This works well, but results in what seems to me an awfull lot of template parameters and a rather ugly typedef mess. I need like 20 lines to typedef all classes I'm going to use, and there are also lots of classes taking a template parameter they do not use themselves, but is needed to be able to typedef another class they do use (which is not per se a bad thing, but in the end everything starts to llok really complicated). Another problem is that when I want to add some functionality to class A and it requires adding a container, class A needs an extra template paramater. As a result, all other classes seeing/using class A suddenly also need that extra parameter leading to a domino effect.
Slightly exaggerated example:
template< class string, class map, class mutex >
class MessageDestination
{
typedef Message< string, map > message_type;
virtual void Eat( const message_type& ) = 0;
}
template< class string, class map, class stream >
class MessageFormatter
{
typedef Message< string, map > message_type;
virtual void Format( const message_type&, stream& ) = 0;
}
template< class string, class map, class containerA,
template< class, class > containerB, template< class, class > class queue, class allocator >
class ThreadedMessageAcceptor
{
typedef Message< string, map > message_type;
typedef MessageDestination< string, map > destination_type;
typedef containerB< destination_type, allocator > destinations_type;
typedef queue< message_type, allocator > messages_type;
};
I can think of some techniques to clean this up, but I'm having a hard time deciding what one or which combination to use. StackOverFlow, your help will be appreciated!
Here's the first solution I thought of, joining parameters together into the type they'll eventually form:
template< class message, class mutex >
class MessageDestination
{
virtual void Eat( const message& ) = 0;
}
This makes it simpler, but doesn't it at the same time it kind of hides what message actually is? Suppose a user wants to provide an implementation, he does not directly see that message has to use a certain string type etc.
Another technique I thougt about, but cannot recall having seen before somwhere which makes it look suspicious, is simply defining everything in a single struct and passing that as single template parameter to everything:
struct MyTemplateParameters
{
typedef std::string string;
typedef std::map map;
typedef std::queue queue;
typedef LightMutex mutex;
template< class A, class B >
struct DefineContainerB
{
typedef containerB< A, B >::type;
}
//....
};
template< class parameters >
class MessageDestination
{
typedef Message< parameters > message_type;
virtual void Eat( const message_type& ) = 0;
};
template< class parameters >
class ThreadedMessageAcceptor
{
typedef Message< parameters > message_type;
typedef MessageDestination< parameters > destination_type;
typedef parameters::DefineContainerB< destination_type, parameters::allocator >::type destinations_type;
};
This is nice as it allows specifying everything at one single point, and the typedefs to all classes will all be class XXX< MyTemplateParameters >, but again, it gives me an uneasy feeling. Is there a reason for this?
The “other” technique is very common in C++. The parameters class is usually called a “trait class”.
This is the way to go (why does it give you an uneasy feeling?). It is used pervasively in the Boost libraries and other C++ libraries. Even the standard library uses it, e.g. in the std::basic_string class.
An equally well-established alternative are metafunctions. At its most basic, a metafunction is a “function” that operates on types rather than objects. So where a function takes value arguments and returns a value, a metafunctions takes template arguments and “returns” a type:
template <typename T>
struct identity {
typedef T type;
};
The metafunction is used (“invoked”) like a normal type definition.
typedef identity<int>::type mytype; // or
identity<int>::type x;
Not very useful in this case. But consider the following common metafunction:
template <typename T>
struct remove_const {
typedef T type;
};
template <typename T>
struct remove_const<T const> {
typedef T type;
};
This can be used to make an arbitrary type (in particular a template argument) non-const. This is actually a type I’m currently using in a project: I have a class that accepts both const and non-const types and offers appropriate interfaces. However, internally I need to store a non-const reference. Simple, I just use the following code in my class:
typename remove_const<T>::type& _reference;
(The typename is required because T is a template argument and that makes remove_const<T>::type a dependent type. Your above example code is actually omitting quite a few required typenames – it won’t compile on several modern compilers!)
Now, how to apply this to your problem?
Create two empty marker types that specify whether your types are used on an embedded device or on a compliant compiler:
struct Embedded { };
struct Compliant { };
Now you can define your classes in terms of these, e.g.:
template<typename Spec>
class ThreadedMessageAcceptor
{
typedef Message< Spec > message_type;
typedef MessageDestination< Spec > destination_type;
typedef typename Allocator< destination_type, Spec >::type allocator_type;
typedef typename ContainerB< destination_type, allocator_type, Spec >::type destinations_type;
};
Here, Spec will be either Compliant or Embedded. So to use it on a standards compliant compiler, write:
ThreadedMessageAcceptor<Compliant> x;
The class uses the following metafunctions:
template <typename T, typename Spec>
struct Allocator { };
template <typename T, typename Alloc, typename Spec>
struct ContainerB { };
You need to remember to specialize them appropriately for your target specifications, e.g.:
template <typename T>
struct Allocator<T, Compliant> {
typedef std::allocator<T> type;
};
template <typename T, typename Alloc>
struct ContainerB<T, Alloc, Compliant> {
typedef std::vector<T, Alloc> type;
};
This already shows that a metafunction may have arbitrarily many arguments besides the Spec (which I’ve put last on a whim – but its placement should be consistent).
To be sure, this is more code than when using a single trait class but it has lower cohesion, logically separates concerns and is easier to reuse.