2009-08-01 3 views
4

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

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

+0

Возможно, вы можете что-то сделать с помощью метапрограммирующих трюков. Но подумайте о том, что произойдет, когда ваш ключ и тип значения будут одинаковыми. Почему не используется typedef для вашего экземпляра? (Обратите внимание, что C++ 0X имеет псевдонимы шаблонов, которые могли бы вам помочь в будущем.) – AProgrammer

+1

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

+0

См. Http://www.informit.com/articles/article.aspx?p=31473 –

ответ

5

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

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

4

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

3

Boost parameters и Boost graph named parameters прилагают усилия к именованию параметров для функций/методов шаблонов. Они дают возможность предоставить аргументы в зависимости от того, какой вы предпочитаете. Некоторые аргументы могут быть необязательными, со значениями по умолчанию.

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

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

0

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

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

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

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

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

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; 
Смежные вопросы