Шаблонная специализация класса, где аргумент шаблона является шаблоном - PullRequest
34 голосов
/ 16 ноября 2010

Интересно, возможно ли что-то похожее на это?По сути, у меня есть шаблонный класс, который иногда принимает объекты шаблонных классов.Я хотел бы специализировать его (или просто функцию-член) для определенного шаблонного класса, но для «универсальной» формы этого класса.

template<typename T, typename S>
class SomeRandomClass
{
    //put something here
};

template<typename T>
class MyTemplateClass
{
    void DoSomething(T & t) {
       //...something
    }
};

template<>
void MyTemplateClass< SomeRandomClass<???> >::DoSomething(SomeRandomClass<???> & t)
{
    //something specialized happens here
}

Замена знаков вопроса на соответствующие типы (двойные и т. Д.) Работает, но я бы хотел, чтобы он оставался общим.Я не знаю, что там поставить, так как любые типы не были бы определены.Я осмотрелся, узнал о параметрах шаблона и попробовал различные комбинации безрезультатно.Спасибо за помощь!

Ответы [ 4 ]

21 голосов
/ 17 ноября 2010

Можно специализировать класс следующим образом

template <>
template <typename T,typename S>
class MyTemplateClass <SomeRandomClass<T,S> >
{
    void DoSomething(SomeRandomClass<T,S>& t) { /* something */ }
};

Невозможно специализировать только метод-член, потому что специализация принадлежит классу в целом, и вам нужно определить новый класс. Вы можете, однако, сделать

template <>
template <typename T,typename S>
class MyTemplateClass <SomeRandomClass<T,S> >
{
    void DoSomething(SomeRandomClass<T,S>& t);
};

template <>
template <typename T,typename S>
void MyTemplateClass<SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S>& t)
{
    // something
}

разделить декларацию и определение.

16 голосов
/ 23 июля 2015

Я не совсем уверен, почему @Ryan Calhoun специализировался так, как он , но вот более краткий пример:

// class we want to specialize with later on
template<typename T, typename S>
struct SomeRandomClass
{
    int myInt = 0;
};

// non-specialized class
template<typename T>
struct MyTemplateClass
{
    void DoSomething(T & t) 
    {
       std::cout << "Not specialized" << std::endl;
    }
};

// specialized class
template<typename T, typename S>
struct MyTemplateClass< SomeRandomClass<T, S> >
{
    void DoSomething(SomeRandomClass<T,S> & t) 
    {
       std::cout << "Specialized" << std::endl;
    }
};

Вы можете видеть, что вам не нужноизбыточный синтаксис, используемый в принятом ответе:

template<>
template<typename T, typename S>

Рабочая демонстрация


Альтернатива

Вы можете использовать type_traits и tag-dispatch в пределахВаш неспециализированный класс, чтобы специализировать только функцию.

Давайте сначала создадим концепцию для is_random_class:

// concept to test for whether some type is SomeRandomClass<T,S>
template<typename T>
struct is_random_class : std::false_type{};

template<typename T, typename S>
struct is_random_class<SomeRandomClass<T,S>> : std::true_type{};

А затем снова объявим наш MyTemplateClass, но на этот раз не шаблонный (потому что мы 'не специализируемся), поэтому мы назовем это MyNonTemplatedClass:

class MyNonTemplatedClass
{

    public:
    template<typename T>
    void DoSomething(T & t) 
    {
       DoSomethingHelper(t, typename is_random_class<T>::type());
    }
    // ...

Заметьте, как DoSomething теперь шаблонизируется, и он фактически вызывает помощника вместо реализации самой логики?

Давайте разберем строку:

DoSomethingHelper(t, typename is_random_class<T>::type());
  • t как и раньше;мы передаем аргумент типа T&
  • typename is_random_class<T>::type()
    • is_random_class<T> - это наша концепция, и так как он происходит от std::true_type или std::false_type, он будет иметь::type определено в классе (Google для "черт типа")
    • ::type() 'создает экземпляр' типа, указанного is_random_class<T>::type.Я говорю это в кавычках, потому что мы действительно собираемся отбросить это, как мы увидим позже
    • typename требуется, потому что компилятор не знает, что is_random_clas<T>::type на самом деле называет тип.

Теперь мы готовы взглянуть на остальную часть MyNonTemplatedClass:

    private:
    //use tag dispatch. If the compiler is smart it won't actually try to instantiate the second param
    template<typename T>
    void DoSomethingHelper(T&t, std::true_type)
    {
        std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
    }

    template<typename T>
    void DoSomethingHelper(T&t, std::false_type)
    {
        std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
    }
};

Полная рабочая демонстрация v2 Здесь

Обратите внимание, что наши вспомогательные функции имеют одинаковые имена, но перегружены типом второго параметра.Мы не даем имя параметру, потому что оно нам не нужно, и, надеюсь, компилятор оптимизирует его, продолжая вызывать правильную функцию.

Наша концепция заставляет DoSomethingHelper(T&t, std::true_type) только если Tимеет тип SomeRandomClass и вызывает другой для любого другого типа.

Преимущество диспетчеризации тегов

Основное преимущество диспетчеризации тегов в том, что вам не нужно специализировать свойвесь класс, если вы хотите специализировать только одну функцию в этом классе.

Диспетчеризация тегов произойдет во время компиляции, чего вы не получите, если попытаетесь выполнить разветвление над концепцией исключительно в функции DoSomething.


C ++17

В C ++ 17 эта проблема становится невероятно простой при использовании шаблонов переменных (C ++ 14) и if constexpr (C ++ 17).

Мы используем наш type_trait для создания шаблона переменной, который даст нам bool значение true, если предоставленный тип T имеет тип SomeRandomClass, и false в противном случае:

template<class T>
constexpr bool is_random_class_v = is_random_class<T>::value;

Тогдамы используем его в выражении if constexpr, которое компилирует только соответствующую ветвь (и отбрасывает другую во время компиляции, поэтому проверка выполняется на время компиляции , а не время выполнения):

struct MyNonTemplatedClass
{
    template<class T>
    void DoSomething(T& t) 
    {
        if constexpr(is_random_class_v<T>)
            std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
        else
            std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
    }
};

type-traits были способом симулировать это без необходимости специализации класса.

Обратите внимание, что is_random_class здесь является заменой произвольного ограничения.В общем, если вы проверяете только один нетемблированный тип, предпочитайте обычную перегрузку, поскольку она более эффективна на компиляторе.

Demo


C++ 20

В C ++ 20 мы можем продвинуться дальше и использовать ограничение вместо if constexpr, используя предложение requiresна нашей шаблонной функции-члена.Недостатком является то, что мы снова возвращаемся к двум функциям;один соответствует ограничению, а другой нет:

struct MyNonTemplatedClass
{
    template<class T>  requires is_random_class_v<T>
    void DoSomething(T& t)
    {
        std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
    }

    template<class T> requires !is_random_class_v<T>
    void DoSomething(T&) 
    {
        std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
    }
};

Демо

10 голосов
/ 16 ноября 2010

Все, что вам нужно сделать, это просто шаблон для того, что вы хотите сохранить универсальным. Взяв то, с чего вы начали:

template<typename T, typename S>
void MyTemplateClass< SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S> & t)
{
    //something specialized happens here
}

EDIT:

В качестве альтернативы, если вы хотите сохранить только часть универсального SomeRandomClass, вы можете:

template<typename T>
void MyTemplateClass< SomeRandomClass<T,int> >::DoSomething(SomeRandomClass<T,int> & t)
{
    //something specialized happens here
}
0 голосов
/ 16 ноября 2010

Редактировать: это правильный ответ на другой вопрос.

Если дважды ввести имя типа T, это немного смущает проблему, поскольку они компилируются отдельно и никак не связаны.

Вы можете перегрузить метод, чтобы получить шаблонный параметр:

template <typename T>
class MyTemplateClass
{
    void DoSomething(T& t) { }

    template <typename U,typename V>
    void DoSomething(SomeRandomClass<<U,V>& r) { }
};

Это сопоставляет U и V в новом методе с T' и S' в SomeRandomClass. В этой настройке U или V может быть того же типа, что и T, но это не обязательно. В зависимости от вашего компилятора, вы должны уметь

MyTemplateClass<string> mine;
SomeRandomClass<int,double> random;

// note: nevermind the non-const ref on the string literal here...
mine.DoSomething("hello world");
mine.DoSomething(random);

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

Edit:

Работа со специализацией шаблона не имеет значения для перегрузки DoSomething. Если вы специализируете класс следующим образом

template <>
class SomeRandomClass <int,double>
{
    // something here...
};

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

Если вы хотите специализировать DoSomething, чтобы взять определенную пару типов для SomeRandomClass, то вы уже потеряли общность ... вот что такое специализация.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...