Вместо нисходящего подхода, начинающегося с вашего фрагмента кода, я возьму восходящий подход, чтобы объяснить некоторые важные детали о шаблонах и используемых инструментах и методах.
В сущности, шаблоны - это инструмент, который позволяет вам писать код на C ++, который применяется к диапазону возможных типов, не строго для фиксированного типа.В языке со статической типизацией это, во-первых, отличный инструмент для повторного использования кода без ущерба для безопасности типов, но, в частности, в C ++ шаблоны очень мощные, поскольку они могут быть специализированными .
КаждымиОбъявление шаблона начинается с ключевого слова template
и списка типа или не тип (то есть значение ) параметров.Параметры типа используют специальное ключевое слово typename
или class
и используются, чтобы позволить вашему коду работать с различными типами.Нетипичные параметры просто используют имя существующего типа, и они позволяют применять ваш код к диапазону значений , которые известны во время компиляции.
Очень простая шаблонная функцияможет выглядеть следующим образом:
template<typename T> // declare a template accepting a single type T
void print(T t){ // print accepts a T and returns void
std::cout << t; // we can't know what this means until the point where T is known
}
Это позволяет нам безопасно повторно использовать код для диапазона возможных типов, и мы можем использовать его следующим образом:
int i = 3;
double d = 3.14159;
std::string s = "Hello, world!";
print<int>(i);
print<double>(d);
print<std::string>(s);
Компилятор даже умныйДостаточно, чтобы вывести параметр шаблона T
для каждого из них, так что вы можете спокойно использовать следующий, функционально идентичный код:
print(i);
print(d);
print(s);
Но предположим, что вы хотите, чтобы print
вел себя по-разному для одного типа,Предположим, например, что у вас есть пользовательский класс Point2D
, который требует специальной обработки.Вы можете сделать это с помощью шаблона специализации :
template<> // this begins a (full) template specialization
void print<Point2D>(Point2D p){ // we are specializing the existing template print with T=Point2D
std::cout << '(' << p.x << ',' << p.y << ')';
}
Теперь, когда мы используем print
с T=Point2D
, выбирается специализация.Это действительно полезно, например, если универсальный шаблон просто не имеет смысла для одного конкретного типа.
std::string s = "hello";
Point2D p {0.5, 2.7};
print(s); // > hello
print(p); // > (0.5,2.7)
Но что, если мы хотим специализировать шаблон для множества типов одновременно, основываясь на простом условии?Это где вещи становятся немного мета.Во-первых, давайте попробуем выразить условие так, чтобы их можно было использовать внутри шаблонов.Это может быть немного сложно, потому что нам нужны ответы во время компиляции.
Условие здесь будет состоять в том, что T
- это число с плавающей запятой, которое истинно, если T=float
или T=double
, и ложно в противном случае.Это на самом деле довольно просто сделать с помощью одной только специализации шаблона.
// the default implementation of is_floating_point<T> has a static member that is always false
template<typename T>
struct is_floating_point {
static constexpr bool value = false;
};
// the specialization is_floating_point<float> has a static member that is always true
template<>
struct is_floating_point<float> {
static constexpr bool value = true;
};
// the specialization is_floating_point<double> has a static member that is always true
template<>
struct is_floating_point<double> {
static constexpr bool value = true;
}
Теперь мы можем запросить любой тип, чтобы узнать, является ли это числом с плавающей запятой:
is_floating_point<std::string>::value == false;
is_floating_point<int>::value == false;
is_floating_point<float>::value == true;
is_floating_point<double>::value == true;
Но как мы можемиспользовать это условие времени компиляции внутри другого шаблона?Как мы можем сказать компилятору, какой шаблон выбрать при наличии множества возможных специализаций шаблонов?
Это достигается за счет использования правила C ++ под названием SFINAE , которое в базовом английском языкеговорит: «когда существует много возможных шаблонов, а текущий не имеет смысла *, просто пропустите его и попробуйте следующий».
* При попытке заменить шаблон есть список ошибокаргументы в шаблонном коде, которые вызывают игнорирование шаблона без немедленной ошибки компилятора .Список немного длинный и сложный .
Один из возможных способов, по которым шаблон не имеет смысла, - это попытка использовать тип, который не существует.
template<T>
void foo(T::nested_type x); // SFINAE error if T does not contain nested_type
Это тот же трюк, который std::enable_if
использует под капотом.enable_if
- это шаблонный класс, принимающий условия типа T
и bool
, и он содержит вложенный тип type
, равный T
, только когда условие истинно .Этого также довольно легко достичь:
template<bool condition, typename T>
struct enable_if {
// no nested type!
};
template<typename T> // partial specialization for condition=true but any T
struct enable_if<true, T> {
typedef T type; // only exists when condition=true
};
Теперь у нас есть помощник, который мы можем использовать вместо любого типа.Если условие, которое мы передаем, является истинным, то мы можем безопасно использовать вложенный тип.Если условие, которое мы передаем, ложно, то шаблон больше не рассматривается.
template<typename T>
std::enable_if<std::is_floating_point<T>::value, void>::type // This is the return type!
numberFunction(T t){
std::cout << "T is a floating point";
}
template<typename T>
std::enable_if<!std::is_floating_point<T>::value, void>::type
numberFunction(T t){
std::cout << "T is not a floating point";
}
Я полностью согласен с тем, что std::enable_if<std::is_floating_point<T>::value, void>::type
- грязный способ расшифровки типа.Вы можете прочитать это как "void
, если T - с плавающей точкой, и бессмысленная чепуха в противном случае."
Наконец, разберем ваш пример:
// we are declaring a template
template<
typename T, // that accepts some type T,
size_t M, // a size_t M,
size_t K, // a size_t K,
size_t N, // a size_t N,
// and an unnamed non-type that only makes sense when T is a floating point
typename std::enable_if_t<std::is_floating_point<T>::value, T> = 0
>
void fastor2d(){//...}
Обратите внимание на = 0
в конце.Это просто значение по умолчанию для окончательного параметра шаблона, и оно позволяет вам указать значения T
, M
, K
и N
, но не пятый параметр.Используемое здесь enable_if
означает, что вы можете предоставить другие шаблоны, называемые fastor2d
, с их собственными наборами условий.