I have my templated container class that looks like this:
template<
class KeyType,
class ValueType,
class KeyCompareFunctor = AnObnoxiouslyLongSequenceOfCharacters<KeyType>,
class ValueCompareFunctor = AnObnoxiouslyLongSequenceOfCharacters<ValueType>
>
class MyClass
{
[...]
}
Which means that when I instantiate an object of this class, I can do it several different ways:
MyClass<MyKeyType, MyValueType> myObject;
MyClass<MyKeyType, MyValueType, MyCustomKeyCompareFunctor> myObject;
MyClass<MyKeyType, MyValueType, MyCustomKeyCompareFunctor, MyCustomValueCompareFunctor> myObject;
Those are all good. The problem comes when I want to instantiate a MyClass that uses a non-default version of the ValueCompareFunctor argument, but I still want to use the default value of the KeyCompareFunctor argument. Then I have to write this:
MyClass<MyKeyType, MyValueType, AnObnoxiouslyLongSequenceOfCharacters<MyKeyType>, MyCustomValueCompareFunctor> myObject;
It would be much more convenient if I could somehow omit the third argument and just write this:
MyClass<KeyType, ValueType, MyCustomValueCompareFunctor> myObject;
Since the MyCustomValueCompareFunctor works only on objects of type MyValueType and not on objects of type MyKeyType, it seems like the compiler could at least theoretically work out what I meant here.
Is there a way to do this in C++?
No. The closest you can come is to allow users to specify some sentinel type - like
void
- meaning "use default value here", and use template metamagic inside your class totypedef
the real default ifvoid
was given to you. But this probably isn't a good idea from readability point of view.In general, both in templates and functions or methods, C++ lets you use default for (and thereby omit) only trailing parameters -- no way out.
I recommend a template or macro to shorten
AnObnoxiouslyLongSequenceOfCharacters<MyKeyType>
toFoo<MyKeyType>
-- not perfect, but better than nothing.Boost parameters and Boost graph named parameters are efforts towards naming parameters for template functions/methods. They give the opportunity to provide arguments in whichever order you prefer. Some arguments may be optional, with default values.
The same approach may be applied to template arguments. Instead of having N template arguments + P optional ones, create your class with N+1 template arguments. The last one will hold "named" parameters which can be omitted.
This answer is not complete yet, but i hope it's a good start !
There is another option, which uses inheritance and which works like the following. For the last two arguments, it uses a class that inherits virtually from a class that has two member templates, that can be used to generate the needed types. Because the inheritance is virtual, the typedefs it declares are shared among the inheritance as seen below.
Now, have a default argument that you use, which has typedefs for the default arguments you want provide. The member templates will be parameterized by the key and value types
Now,
use_policies
will derive from all the template arguments. Where a derived class ofVirtualRoot
hides a member from the base, that member of the derived class is dominant over the member of the base, and will be used, even though the base-class member can be reached by other path in the inheritance tree.Note that you don't pay for the virtual inheritance, because you never create an object of type
use_policies
. You only use virtual inheritance to make use of the dominance rule.Because we potentially derive from the same class more than once, we use a class template
Inherit
: Inheriting the same class directly twice is forbidden. But inheriting it indirectly is allowed. You can now use this all like the following:An alternative option is to use Traits classes:
Then if you have a type which needs a different comparison function for Key's, then you'd explicitly specialize the KeyTraits type for that case. Here's an example where we change it for
int
: