C ++: default values ​​for template arguments different from last?

I have my template container class that looks like this:

template< class KeyType, class ValueType, class KeyCompareFunctor = AnObnoxiouslyLongSequenceOfCharacters<KeyType>, class ValueCompareFunctor = AnObnoxiouslyLongSequenceOfCharacters<ValueType> > class MyClass { [...] } 

This means that when I instantiate an object of this class, I can do this in several ways:

 MyClass<MyKeyType, MyValueType> myObject; MyClass<MyKeyType, MyValueType, MyCustomKeyCompareFunctor> myObject; MyClass<MyKeyType, MyValueType, MyCustomKeyCompareFunctor, MyCustomValueCompareFunctor> myObject; 

All this is good. The problem occurs when I want to create an instance of MyClass that uses a non-standard 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 MyCustomValueCompareFunctor only works on objects of type MyValueType, and not on objects of type MyKeyType, it seems that the compiler can at least theoretically determine what I had in mind here.

Is there any way to do this in C ++?

+4
source share
5 answers

In general, both in templates and in functions or methods, C ++ allows you to use by default only (and thereby omit) pulling parameters - there is no way out.

I recommend a template or macro to shorten AnObnoxiouslyLongSequenceOfCharacters<MyKeyType> to Foo<MyKeyType> - not perfect, but better than nothing.

+5
source

No. The closest thing you can do is allow users to specify some type of sentinel type, for example void , which means “use the default value here” and use the template metamagic inside your class to typedef real default value if void was provided to you, But this is probably not a good idea in terms of readability.

+4
source

Enhancement options and Enlarge graph with parameter names are the efforts to define name parameters / template methods. They provide an opportunity to provide arguments depending on which one you prefer. Some arguments may be optional, with default values.

The same approach can be applied to template arguments. Instead of having N template arguments + P optional, create your own class with template arguments N + 1. The latter will contain "named" parameters that can be omitted.

This answer is not complete yet, but I hope this is a good start!

+3
source

An alternative is to use the Traits classes:

 template <class KeyType> class KeyTraits { typedef AnObnoxiouslyLongSequenceOfCharacters<KeyType> Compare; }; template <class ValueType> class ValueTraits { typedef AnObnoxiouslyLongSequenceOfCharacters<ValueType> Compare; }; template<class KeyType class ValueType> class MyClass { typedef KeyTraits<KeyType>::Compare KeyCompareFunctor; typedef ValueTraits<ValueType>::Compare KeyCompareFunctor; }; 

Then, if you have a type that needs another comparison function for Key's, then you explicitly specialize the KeyTraits type for this case. Here is an example where we change it to int :

 template <> class KeyTraits<int> { typedef SpecialCompareForInt Cmopare; }; 
0
source

There is another option that uses inheritance and works as follows. For the last two arguments, it uses a class that actually inherits from a class that has two member templates that can be used to generate the necessary types. Because inheritance is virtual, the identifiers declared by it are shared between inheritance, as shown below.

 template<class KeyType, class ValueType, class Pol1 = DefaultArgument, class Pol2 = DefaultArgument> class MyClass { typedef use_policies<Pol1, Pol2> policies; typedef KeyType key_type; typedef ValueType value_type; typedef typename policies:: template apply_key_compare<KeyType>::type key_compare; typedef typename policies:: template apply_value_compare<ValueType>::type value_compare; }; 

Now you have the default argument that you are using, which has typedefs for the default arguments that you want to provide. Member templates will be parameterized with keys and value types.

 struct VirtualRoot { template<typename KeyType> struct apply_key_compare { typedef AnObnoxiouslyLongSequenceOfCharacters<KeyType> type; }; template<typename ValueType> struct apply_value_compare { typedef AnObnoxiouslyLongSequenceOfCharacters<ValueType> type; }; }; struct DefaultArgument : virtual VirtualRoot { }; template<typename T> struct KeyCompareIs : virtual VirtualRoot { template<typename KeyType> struct apply_key_compare { typedef T type; }; }; template<typename T> struct ValueCompareIs : virtual VirtualRoot { template<typename ValueType> struct apply_value_compare { typedef T type; }; }; 

use_policies will now be inferred from all template arguments. If the derived class VirtualRoot hides a member from the base, this member of the derived class will dominate the member and will be used, although the base class element can be reached in a different way in the Inheritance Tree.

Please note that you do not pay for virtual inheritance, because you never create an object of type use_policies . You use only virtual inheritance to use the dominance rule.

 template<typename B, int> struct Inherit : B { }; template<class Pol1, class Pol2> struct use_policies : Inherit<Pol1, 1>, Inherit<Pol2, 2> { }; 

Since we potentially exit the same class more than once, we use the Inherit template template: Inheriting from the same class directly is twice forbidden. But his inheritance is indirectly permitted. Now you can use all this as:

 MyClass<int, float> m; MyClass<float, double, ValueCompareIs< less<double> > > m; 
0
source

All Articles