Я не совсем уверен, почему @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";
}
};
Демо