C ++: Значения по умолчанию для аргументов шаблона, кроме последних? - PullRequest
5 голосов
/ 01 августа 2009

У меня есть шаблонный класс контейнера, который выглядит следующим образом:

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

Это означает, что когда я создаю экземпляр объекта этого класса, я могу сделать это несколькими различными способами:

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

Это все хорошо. Проблема возникает, когда я хочу создать экземпляр MyClass, который использует версию аргумента ValueCompareFunctor, отличную от версии по умолчанию, но я все еще хочу использовать значение по умолчанию аргумента KeyCompareFunctor. Тогда я должен написать это:

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

Было бы гораздо удобнее, если бы я мог как-то опустить третий аргумент и просто написать это:

MyClass<KeyType, ValueType, MyCustomValueCompareFunctor> myObject;

Так как MyCustomValueCompareFunctor работает только с объектами типа MyValueType, а не с объектами типа MyKeyType, похоже, что компилятор может хотя бы теоретически понять, что я имел в виду здесь.

Есть ли способ сделать это в C ++?

Ответы [ 5 ]

5 голосов
/ 01 августа 2009

В целом, как в шаблонах, так и в функциях или методах, C ++ позволяет использовать значение по умолчанию только для (и, следовательно, пропустить) только конечных параметров - без выхода.

Я рекомендую шаблон или макрос сократить AnObnoxiouslyLongSequenceOfCharacters<MyKeyType> до Foo<MyKeyType> - не идеально, но лучше, чем ничего.

4 голосов
/ 01 августа 2009

Нет. Самое близкое, что вы можете сделать, это позволить пользователям указать некоторый тип часового типа, например void, что означает «использовать значение по умолчанию здесь», и использовать шаблон metamagic внутри вашего класса, чтобы typedef реальное значение по умолчанию, если вам было дано void , Но это, вероятно, не очень хорошая идея с точки зрения читабельности.

3 голосов
/ 01 августа 2009

Параметры повышения и График повышения именованных параметров - это попытки присвоить имена параметрам шаблонных функций / методов. Они дают возможность приводить аргументы в любом порядке, который вы предпочитаете. Некоторые аргументы могут быть необязательными, со значениями по умолчанию.

Тот же подход может быть применен к аргументам шаблона. Вместо того, чтобы иметь N шаблонных аргументов + P необязательных, создайте свой класс с N + 1 шаблонными аргументами. Последний будет содержать «именованные» параметры, которые могут быть опущены.

Этот ответ еще не завершен, но я надеюсь, что это хорошее начало!

0 голосов
/ 29 августа 2009

Существует еще одна опция, которая использует наследование и которая работает следующим образом. Для последних двух аргументов он использует класс, который наследуется практически от класса, имеющего два шаблона членов, которые можно использовать для генерации необходимых типов. Поскольку наследование является виртуальным, объявленные им typedefs распределяются между наследниками, как показано ниже.

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

Теперь у вас есть аргумент по умолчанию, который вы используете, который имеет typedefs для аргументов по умолчанию, которые вы хотите предоставить. Шаблоны элементов будут параметризованы ключом и типом значения

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 будет производным от всех аргументов шаблона. Если производный класс VirtualRoot скрывает член от базы, этот член производного класса доминирует над членом базы и будет использоваться, даже если член базового класса может быть достигнут другим путем в наследственное дерево.

Обратите внимание, что вы не платите за виртуальное наследование, потому что вы никогда не создаете объект типа use_policies. Вы используете только виртуальное наследование, чтобы использовать правило доминирования.

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

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

Поскольку мы потенциально являемся производными от одного и того же класса более одного раза, мы используем шаблон класса Inherit: наследование одного и того же класса напрямую дважды запрещено. Но косвенное наследование разрешено. Теперь вы можете использовать все это следующим образом:

MyClass<int, float> m;
MyClass<float, double, ValueCompareIs< less<double> > > m;
0 голосов
/ 03 августа 2009

Альтернативный вариант - использовать классы черт:

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

Тогда, если у вас есть тип, которому нужна другая функция сравнения для ключей, вы бы явно специализировали тип KeyTraits для этого случая. Вот пример, где мы меняем его на int:

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