views:

862

answers:

5

Hi all,

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++?

+3  A: 

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 to typedef the real default if void was given to you. But this probably isn't a good idea from readability point of view.

Pavel Minaev
+4  A: 

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> to Foo<MyKeyType> -- not perfect, but better than nothing.

Alex Martelli
+2  A: 

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 !

Benoît
A: 

An alternative option is to use 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 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:

template <>
class KeyTraits<int>
{
  typedef SpecialCompareForInt Cmopare;
};
Richard Corden
A: 

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.

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, 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

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;
  };
};

Now, use_policies will derive from all the template arguments. Where a derived class of VirtualRoot 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.

template<typename B, int>
struct Inherit : B { };

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

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:

MyClass<int, float> m;
MyClass<float, double, ValueCompareIs< less<double> > > m;
Johannes Schaub - litb