Объявление шаблона
Объявление шаблонного класса или шаблонной функции в примере ниже:
template <typename T>
struct vector {
void push_back(T const &);
T const& operator[](size_t index) const;
template <typename U>
void g(T, U);
};
template <typename T>
void f(T&& a, T&& b) {
std::cout << a + b << std::endl;
}
Вместо T и U будет подставляться тот тип, который был указан в шаблонном параметре при использовании класса/функции. Также в большинстве случаев компилятор может вывести тип самостоятельно без явного его указания.
vector<int> v;
vector<double> v2;
v.template g<float>(42, 42.0);
v.g(42, 42.0); // same
f(1, 3); // 4
f(1.5, 2.5); // 4.0
Специализации
Специализация - это выделенная реализация для каких-то указанных типов. К примеру из примера выше для vector<std::string>
и void f(bool&&, bool&&)
я бы хотел иметь другое тело функции. Это пример надуманный, конечно.
template <>
struct vector<std::string> {
/* ... */
};
template <>
void f<bool>(bool&& a, bool&& b) {
std::cout << (a | b) << std::endl;
}
Специализацию можно вводить не целиком, а частично, оставляя свободными другие шаблонные параметры. В качестве примера рассмотрим простенькую реализацию std::conditional
.
template <bool Cond, typename IfTrue, typename IfFalse>
struct conditional;
template <typename IfTrue1, typename IfFalse1>
struct conditional<false, IfTrue1, IfFalse1> { // partial specialization
typedef IfFalse1 type;
};
template <typename IfTrue1, typename IfFalse1>
struct conditional<true, IfTrue1, IfFalse1> { // partial specialization
typedef IfTrue1 type;
};
Специализациям свойственен большее высокий приоритет при разрешениях перегрузок (overload resolution).
template <typename T>
struct vector<T*> {
/* ... */
}
vector<foo*> v; // выберется эта специализация
Пример неразрешимой перегрузки:
template <typename U, typename V>
struct mytype {};
template <typename U, typename V>
struct mytype<U*, V> {};
template <typename U, typename V>
struct mytype<U, V*> {};
mytype<long*, double*> f;
Мы получим ошибку, так как есть два равноправных кандидата. Исправить это можно определением еще одной, более подходящей специализации:
template <typename U, typename V>
struct mytype<U*, V*> {};
Ошибка разделения на объявление и реализацию
Давайте представим, как мы могли бы писать код с шаблонными функциями, используя разделение на объявление и реализацию, как полагается.
// util.h
template <typename T>
void f(T&, T&);
// util.cpp
template <typename T>
void f(T& a, T& b) {
std::cout << a + b << std::endl;
}
// main.cpp
#include "util.h"
int main(){
int a, b;
f(a, b);
}
Но в данном примере, к сожалению, мы получим ошибку компиляции. Так происходит, потому что генерация и подстановка кода шаблонов (инстанцирование) происходит до линковки и после компиляции каждой отдельной единицы трансляции. Компилятор, обрабатывая util.cpp
, не знает о том, что кто-то будет вызывать f(int, int)
в других единицах трансляции.
На самом деле все шаблонные функции неявно являются inline
, поэтому их реализацию можно сразу писать в .h
файле.
Инстанцирование
В стандарте прописано, что инстанцирование происходит только когда это необходимо. При этом компилятор может делать это в конце единицы трансляции.
В следующем примере приводится случай, который это показывает:
template <typename T>
struct foo {
T* a;
void f(){
T a;
}
};
int main() {
foo<void> a; // так скомпилируется
a.f(); // а так нет, ошибка из-за void a
}
Генерация и подстановка по требованию, так сказать. С классами работает аналогично: полное тело класса не подставляется, если не требуется. Пример:
template <typename T>
struct foo {
T a;
};
int main() {
foo<void>* a; // так скомпилируется
a->a; // а так нет, опять ошибка из-за void a
}
Явное инстанцирование
Пусть мы не хотим, чтобы одни и те же лишние инстанцирования были в разных единицах трансляции. Чтобы этого избежать, можно делать так:
template <typename T>
void foo(T) {}
template void foo<int>(int); // генерирует тело функции в этом месте
template void foo<float>(float);
template void foo<double>(double);
Подавление инстанцирования
Пусть мы знаем, что функции уже где-то инстанцированы и мы не хотим лишних:
extern template void foo<int>(int);
extern template void foo<float>(float);
Выдаём тело наружу и говорим, что уже проинстанцировано. main
не будет пытаться инстанцировать функцию, так как увидит extern
и будет работать соответствующе.
Теперь зная про шаблоны и специализации можно творить всякую магию:
Подсчет факториала в compile-time
Осторожно, при отрицательных значениях компилятор может надолго зависнуть.
template <int T>
struct factorial {
static const int result = T * factorial<T - 1>::result;
};
template <>
struct factorial<0> {
static const int result = 1;
};
static_assert(factorial<0>::result == 1);
static_assert(factorial<3>::result == 6);
static_assert(factorial<5>::result == 120);
Заимствования:
cpp-notes/11_templates.md at master · lejabque/cpp-notes (github.com)