Подтвердить что ты не робот

Специализированная специализированная специализация класса, в которой шаблонный шаблон является шаблоном

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

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
}

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

4b9b3361

Ответ 1

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

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

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

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
}

чтобы разделить объявление и определение.

Ответ 2

Я не совсем уверен, почему @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 концепцию для 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.


С++ 17

В С++ 17 эта проблема становится невероятно простой, если использовать переменные шаблоны (С++ 14) и if constexpr (С++ 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";
    }
};

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

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

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


С++ 20

В С++ 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";
    }
};

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

Ответ 3

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

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

EDIT:

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

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

Ответ 4

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

Использование typename 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, то вы уже потеряли общность... что какая специализация.