Шаблон функции
Шаблон функции определяет семейство функций.
Синтаксис
template < список параметров > объявление функции | (1) | |
template < список параметров > requires ограничение объявление функции | (2) | (с C++20) |
| объявление функции с плейсхолдерами | (3) | (с C++20) |
export template < список параметров > объявление функции | (4) | (удалено в C++11) |
Объяснение
| список параметров | - | непустой список шаблонов параметров, разделённых запятыми, каждый из которых является либо параметром нетипового типа, либо параметром типа, либо параметром шаблона, либо упаковкой параметров(с C++11). Как и для любого шаблона, параметры могут быть ограничены(с C++20) |
| объявление функции | - | объявление функции. Название объявленной функции становится именем шаблона. |
| ограничение | - | выражение ограничения, которое ограничивает шаблоны параметров, принимаемые этим шаблоном функции. |
| объявление функции- с плейсхолдерами | - | объявление функции, где тип хотя бы одного параметра использует плейсхолдер auto или Concept auto: список параметров шаблона будет иметь один вымышленный параметр для каждого плейсхолдера (см. Сокращённые шаблоны функций ниже) |
|
| (до C++11) |
Сокращённый шаблон функцииКогда типы-заменители (либо auto, либо Concept auto) появляются в списке параметров объявления функции или объявления шаблона функции, объявление объявляет шаблон функции, и один вымышленный параметр шаблона для каждого заменителя добавляется в список параметров шаблона: void f1(auto); // same as template<class T> void f1(T) void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept void f3(C2 auto...); // same as template<C2... Ts> void f3(Ts...), if C2 is a concept void f4(const C3 auto*, C4 auto&); // same as template<C3 T, C4 U> void f4(const T*, U&); template<class T, C U> void g(T x, U y, C auto z); // same as template<class T, C U, C W> void g(T x, U y, W z); Сокращённые шаблоны функций могут быть специализированы, как и все шаблоны функций. template<> void f4<int>(const int*, const double&); // specialization of f4<int, const double> | (с C++20) |
Экземпляризация шаблона функции
Сам по себе шаблон функции не является типом, функцией или любым другим объектом. Ни один код не генерируется из исходного файла, который содержит только определения шаблонов. Для того, чтобы любой код появился, шаблон должен быть экземпляризирован: аргументы шаблона должны быть определены, чтобы компилятор мог сгенерировать фактическую функцию (или класс из шаблона класса).
Явное объявление
template тип возврата имя < список аргументов > ( список параметров ) ; | (1) | |
template тип возврата имя ( список параметров ) ; | (2) | |
extern template тип возврата имя < список аргументов > ( список параметров ) ; | (3) | (с C++11) |
extern template тип возврата имя ( список параметров ) ; | (4) | (с C++11) |
Определение явного экземпляра заставляет экземпляризировать функцию или член-функцию, на которую оно ссылается. Оно может появиться в программе где угодно после определения шаблона, и для данного списка аргументов допускается только один раз в программе; диагностика не требуется.
| Объявление явного экземпляра (extern template) предотвращает неявные экземпляризации: код, который в противном случае вызвал бы неявную экземпляризацию, должен использовать определение явного экземпляра, предоставленное где-то ещё в программе. | (с C++11) |
Завершающий аргумент шаблона может быть оставлен неопределённым в явном экземпляре специализации шаблона функции или специализации шаблона члена-функции, если его можно вывести из параметра функции:
template<typename T>
void f(T s)
{
std::cout << s << '\n';
}
template void f<double>(double); // instantiates f<double>(double)
template void f<>(char); // instantiates f<char>(char), template argument deduced
template void f(int); // instantiates f<int>(int), template argument deducedЯвное объявление шаблона функции или шаблона члена-функции класса не может использовать inline или constexpr. Если объявление явного экземпляра называет неявную специальную член-функцию, программа является ошибочной.
Явное объявление конструктора не может использовать список параметров шаблона (синтаксис (1)), что также никогда не является необходимым, потому что они могут быть выведены (синтаксис (2)).
| Явное объявление проспективного деструктора должно назвать выбранный деструктор класса. | (с C++20) |
Объявления явных экземпляров не подавляют неявную экземпляризацию inline-функций, auto-объявлений, ссылок и специализаций шаблонов классов. (следовательно, когда inline-функция, которая является предметом явного объявления экземпляра, используется в соответствии с ODR, она неявно экземпляризуется для инлайнинга, но её вне-строчный экземпляр не генерируется в этом модуле)
Определение явного экземпляра шаблона функции с аргументами по умолчанию не является использованием аргументов и не пытается их инициализировать:
char* p = 0;
template<class T>
T g(T x = &p) { return x; }
template int g<int>(int); // OK even though &p isn’t an int.Неявная экземпляризация
Когда код ссылается на функцию в контексте, который требует существования определения функции, или если существование определения влияет на семантику программы(с C++11), и эта конкретная функция не была явно экземпляризирована, происходит неявная экземпляризация. Список аргументов шаблона не должен быть предоставлен, если он может быть выведен из контекста.
#include <iostream>
template<typename T>
void f(T s)
{
std::cout << s << '\n';
}
int main()
{
f<double>(1); // instantiates and calls f<double>(double)
f<>('a'); // instantiates and calls f<char>(char)
f(7); // instantiates and calls f<int>(int)
void (*pf)(std::string) = f; // instantiates f<string>(string)
pf("∇"); // calls f<string>(string)
}| Существование определения функции считается влияющим на семантику программы, если функция нужна для вычисления константного выражения выражением, даже если вычисление константного выражения не требуется или если вычисление константного выражения не использует определение. template<typename T>
constexpr int f() { return T::value; }
template<bool B, typename T>
void g(decltype(B ? f<T>() : 0));
template<bool B, typename T>
void g(...);
template<bool B, typename T>
void h(decltype(int{B ? f<T>() : 0}));
template<bool B, typename T>
void h(...);
void x()
{
g<false, int>(0); // OK: B ? f<T>() : 0 is not potentially constant evaluated
h<false, int>(0); // error: instantiates f<int> even though B evaluates to false
// and list-initialization of int from int cannot be narrowing
} | (с C++11) |
Примечание: полное исключение <> позволяет механизму разрешения перегрузки исследовать как шаблоны, так и нешаблонные перегрузки.
Вывод шаблонных аргументов
Для того, чтобы инстанцировать шаблон функции, необходимо знать все шаблонные аргументы, но не каждый аргумент обязательно нужно указывать. Если это возможно, компилятор выведет недостающие шаблонные аргументы из аргументов функции. Это происходит при попытке вызова функции и при взятии адреса шаблона функции.
template<typename To, typename From>
To convert(From f);
void g(double d)
{
int i = convert<int>(d); // calls convert<int,double>(double)
char c = convert<char>(d); // calls convert<char,double>(double)
int(*ptr)(float) = convert; // instantiates convert<int, float>(float)
}Этот механизм позволяет использовать шаблоны операторов, поскольку нет синтаксиса для указания шаблонных аргументов для оператора, кроме как переписать его как выражение вызова функции.
#include <iostream>
int main()
{
std::cout << "Hello, world" << std::endl;
// operator<< is looked up via ADL as std::operator<<,
// then deduced to operator<<<char, std::char_traits<char>> both times
// std::endl is deduced to &std::endl<char, std::char_traits<char>>
}Вывод шаблонных аргументов происходит после поиска имени шаблона функции (что может включать поиск зависимых аргументов) и перед разрешением перегрузки.
Подробности см. в разделе Вывод шаблонных аргументов.
Явные шаблоны аргументов
Шаблонные аргументы шаблона функции могут быть получены из
- вывода шаблонных аргументов
- аргументов по умолчанию
- явного указания, что можно сделать в следующих контекстах:
- в выражении вызова функции
- при взятии адреса функции
- при инициализации ссылки на функцию
- при формировании указателя на член-функцию
- в явной специализации
- в явной инстанциации
- в объявлении друга
Нет способа явно указать шаблоны аргументов для перегруженных операторов, функций преобразования и конструкторов, поскольку они вызываются без использования имени функции.
Указанные шаблонные аргументы должны соответствовать шаблонам параметров по типу (т.е., тип для типа, нетиповой для нетипового и шаблон для шаблона). Нельзя указать больше аргументов, чем параметров (если один параметр является параметром-упаковкой, в этом случае для каждого параметра, не являющегося параметром-упаковкой, должен быть аргумент)(с C++11).
Указанные нетиповые аргументы должны либо соответствовать типам соответствующих нетиповых шаблонных параметров, либо быть преобразуемы к ним.
Параметры функции, которые не участвуют в выводе шаблонных аргументов (например, если соответствующие шаблоны аргументов указаны явно), подвергаются неявным преобразованиям к типу соответствующего параметра функции (как и в обычном разрешении перегрузки).
| Параметр-упаковку шаблона, который явно указан, можно расширить с помощью вывода шаблонных аргументов, если есть дополнительные аргументы: template<class... Types>
void f(Types... values);
void g()
{
f<int*, float*>(0, 0, 0); // Types = {int*, float*, int}
} | (с C++11) |
Подстановка шаблонных аргументов
Когда все шаблоны аргументов были указаны, выведены или получены из параметров шаблона по умолчанию, каждое использование шаблона параметра в списке параметров функции заменяется соответствующими шаблонами аргументов.
Ошибка подстановки (т.е. невозможность заменить шаблоны параметров выведенными или предоставленными шаблонами аргументов) шаблона функции удаляет шаблон функции из множества разрешений перегрузки. Это позволяет множеством способов манипулировать множествами разрешений перегрузки с помощью шаблонного метапрограммирования: см. SFINAE для получения более подробной информации.
После подстановки все параметры функции типа массива и функции корректируются до указателей, и все верхние cv-квалификаторы удаляются из параметров функции (как в обычном объявлении функции).
Удаление верхнего cv-квалификатора не влияет на тип параметра, так как он появляется внутри функции:
template<class T>
void f(T t);
template<class X>
void g(const X x);
template<class Z>
void h(Z z, Z* zp);
// two different functions with the same type, but
// within the function, t has different cv qualifications
f<int>(1); // function type is void(int), t is int
f<const int>(1); // function type is void(int), t is const int
// two different functions with the same type and the same x
// (pointers to these two functions are not equal,
// and function-local statics would have different addresses)
g<int>(1); // function type is void(int), x is const int
g<const int>(1); // function type is void(int), x is const int
// only top-level cv-qualifiers are dropped:
h<const int>(1, NULL); // function type is void(int, const int*)
// z is const int, zp is const int*Перегрузка шаблонов функций
Шаблоны функций и нешаблонные функции могут быть перегружены.
Нешаблонная функция всегда отличается от специализации шаблона с тем же типом. Специализации разных шаблонов функций всегда отличаются друг от друга, даже если у них один и тот же тип. Два шаблона функции с одинаковым типом возврата и одинаковым списком параметров являются различными и могут быть выделены со списком явных шаблонов аргументов.
Когда выражение, использующее шаблонные параметры типа или нетипа, появляется в списке параметров функции или в типе возврата, это выражение остается частью сигнатуры шаблона функции в целях перегрузки:
template<int I, int J> A<I+J> f(A<I>, A<J>); // overload #1 template<int K, int L> A<K+L> f(A<K>, A<L>); // same as #1 template<int I, int J> A<I-J> f(A<I>, A<J>); // overload #2
Два выражения, включающие шаблоны параметров, называются эквивалентными, если два определения функций, содержащие эти выражения, были бы одинаковыми в соответствии с правилом одного определения, т.е. два выражения содержат одинаковую последовательность токенов, имена которых разрешаются до одних и тех же сущностей посредством поиска имен, за исключением того, что шаблоны параметров могут иметь разные имена. Два выражения лямбда никогда не являются эквивалентными.(с C++20)
template<int I, int J> void f(A<I+J>); // template overload #1 template<int K, int L> void f(A<K+L>); // equivalent to #1
При определении, являются ли два зависимые выражения эквивалентными, учитываются только участвующие зависимые имена, а не результаты поиска имен. Если несколько объявлений одного шаблона отличаются результатом поиска имен, используется первое такое объявление:
template<class T>
decltype(g(T())) h(); // decltype(g(T())) is a dependent type
int g(int);
template<class T>
decltype(g(T())) h()
{ // redeclaration of h() uses earlier lookup
return g(T()); // although the lookup here does find g(int)
}
int i = h<int>(); // template argument substitution fails; g(int)
// was not in scope at the first declaration of h()Два шаблона функций считаются эквивалентными, если
- они объявлены в одном и том же области видимости
- они имеют одинаковое имя
- они имеют эквивалентные списки шаблонов параметров, что означает, что списки имеют одинаковую длину, и для каждой соответствующей пары параметров верно следующее:
- два параметра имеют одинаковый тип (оба типа, оба нетипа или оба шаблона)
| (с C++11) |
- если нетиповые, их типы эквивалентны,
- если шаблоны, их шаблоны параметров эквивалентны,
| (с C++20) |
- выражения, содержащие шаблоны параметров в их типах возврата и списках параметров, являются эквивалентными
| (с C++20) |
Два возможно оцениваемые(с C++20) выражения, включающие шаблоны параметров, называются функционально эквивалентными, если они не эквивалентны, но для любого набора шаблонов аргументов вычисление двух выражений приводит к одному и тому же значению.
Два шаблона функций считаются функционально эквивалентными, если они эквивалентны, за исключением того, что одно или несколько выражений, включающих шаблоны параметров в их типах возврата и списках параметров, являются функционально эквивалентными.
| Кроме того, два шаблона функций являются функционально эквивалентными, но не эквивалентными, если их ограничения указаны по-разному, но они принимают и удовлетворяются одним и тем же набором списков шаблонов аргументов. | (с C++20) |
Если программа содержит объявления шаблонов функций, которые функционально эквивалентны, но не эквивалентны, программа некорректна; диагностика не требуется.
// equivalent template<int I> void f(A<I>, A<I+10>); // overload #1 template<int I> void f(A<I>, A<I+10>); // redeclaration of overload #1 // not equivalent template<int I> void f(A<I>, A<I+10>); // overload #1 template<int I> void f(A<I>, A<I+11>); // overload #2 // functionally-equivalent but not equivalent // This program is ill-formed, no diagnostic required template<int I> void f(A<I>, A<I+10>); // overload #1 template<int I> void f(A<I>, A<I+1+2+3+4>); // functionally equivalent
Когда один и тот же специализированный шаблон функции соответствует более чем одному перегруженному шаблону функции (это часто происходит из-за вывода шаблонов аргументов), выполняется частичный порядок перегруженных шаблонов функций для выбора лучшего совпадения.
Конкретно, частичный порядок выполняется в следующих ситуациях:
template<class X> void f(X a); template<class X> void f(X* a); int* p; f(p);
template<class X> void f(X a); template<class X> void f(X* a); void (*p)(int*) = &f;
template<class X>
void f(X a); // first template f
template<class X>
void f(X* a); // second template f
template<>
void f<>(int *a) {} // explicit specialization
// template argument deduction comes up with two candidates:
// f<int*>(int*) and f<int>(int*)
// partial ordering selects f<int>(int*) as more specializedНеформально «A более специализирован, чем B» означает «A принимает меньше типов, чем B».
Формально, для определения, какой из двух шаблонов функций более специализирован, процесс частичного упорядочения сначала преобразует один из двух шаблонов следующим образом:
- Для каждого типа, нетипа и параметра шаблона, включая пакеты параметров,(с C++11) генерируется уникальный вымышленный тип, значение или шаблон и подставляется в тип функции шаблона
- Если только один из двух шаблонов функций, которые сравниваются, является членом функции, и этот шаблон функции является нестатическим членом некоторого класса
A, в его список параметров вставляется новый первый параметр. Учитывая cv как cv-квалификаторы шаблона функции и ref как ref-квалификатор шаблона функции(с C++11), новый тип параметра — cvA&если ref не&&, или ref отсутствует, а первый параметр другого шаблона имеет тип ссылки на rvalue, в этом случае тип — cvA&&(с C++11). Это помогает в упорядочении операторов, которые ищутся как как члены функции, так и как свободные функции:
struct A {};
template<class T>
struct B
{
template<class R>
int operator*(R&); // #1
};
template<class T, class R>
int operator*(T&, R&); // #2
int main()
{
A a;
B<A> b;
b * a; // template argument deduction for int B<A>::operator*(R&) gives R=A
// for int operator*(T&, R&), T=B<A>, R=A
// For the purpose of partial ordering, the member template B<A>::operator*
// is transformed into template<class R> int operator*(B<A>&, R&);
// partial ordering between
// int operator*( T&, R&) T=B<A>, R=A
// and int operator*(B<A>&, R&) R=A
// selects int operator*(B<A>&, A&) as more specialized
}После преобразования одного из двух шаблонов, как описано выше, выполняется выведение аргументов шаблона с использованием выведенного шаблона в качестве шаблона аргумента и исходного типа шаблона другого шаблона в качестве параметра шаблона. Затем процесс повторяется, используя второй шаблон (после преобразований) в качестве аргумента и первый шаблон в его исходной форме в качестве параметра.
Типы, используемые для определения порядка, зависят от контекста:
- в контексте вызова функции типы — это типы параметров функции, для которых вызов функции имеет аргументы (аргументы функций по умолчанию, пакеты параметров,(с C++11) и параметры с эллипсисом не рассматриваются — см. примеры ниже)
- в контексте вызова функции пользовательского преобразования используются возвращаемые типы шаблонов функций преобразования
- в других контекстах используется тип шаблона функции
Каждый тип из списка выше из шаблона параметра выводится. Перед началом выведения каждый параметр P шаблона параметра и соответствующий аргумент A шаблона аргумента корректируются следующим образом:
- Если оба
PиAявляются типами ссылок, определить, какой из них имеет более высокие cv-квалификации (во всех остальных случаях cv-квалификации игнорируются для целей частичного упорядочения) - Если
P— тип ссылки, он заменяется типом, на который ссылается - Если
A— тип ссылки, он заменяется типом, на который ссылается - Если
Pимеет cv-квалификации,Pзаменяется на его cv-неквалифицированную версию - Если
Aимеет cv-квалификации,Aзаменяется на его cv-неквалифицированную версию
После этих корректировок выполняется выведение P из A в соответствии с выведением аргументов шаблона из типа.
| Если Если | (с C++11) |
Если аргумент A преобразованного шаблона-1 может быть использован для вывода соответствующего параметра P шаблона-2, но не наоборот, то этот A более специализирован, чем P относительно типов, которые выводятся этой P/A парой.
Если выведение успешно в обоих направлениях, и исходные P и A были типами ссылок, то выполняются дополнительные проверки:
- Если
Aбыла ссылкой на lvalue, аP— ссылкой на rvalue,Aсчитается более специализированной, чемP - Если
Aимела более высокие cv-квалификации, чемP,Aсчитается более специализированной, чемP
Во всех остальных случаях ни один шаблон не является более специализированным, чем другой, относительно типов, выведенных этой P/A парой.
После рассмотрения каждой P и A в обоих направлениях, если для каждого рассматриваемого типа
- шаблон-1 не менее специализирован, чем шаблон-2 для всех типов
- шаблон-1 более специализирован, чем шаблон-2 для некоторых типов
- шаблон-2 не более специализирован, чем шаблон-1 для каких-либо типов ИЛИ не не менее специализирован для каких-либо типов
то шаблон-1 более специализирован, чем шаблон-2. Если условия выше верны после перемены порядка шаблонов, то шаблон-2 более специализирован, чем шаблон-1. В противном случае ни один шаблон не является более специализированным, чем другой.
| В случае ничьи, если один шаблон функции имеет хвостовой пакет параметров, а другой — нет, то шаблон с опущенным параметром считается более специализированным, чем шаблон с пустым пакетом параметров. | (с C++11) |
Если после рассмотрения всех пар перегруженных шаблонов один шаблон однозначно более специализирован, чем все остальные, то выбирается специализация этого шаблона, в противном случае компиляция терпит неудачу.
В следующих примерах вымышленные аргументы будут называться U1, U2:
template<class T>
void f(T); // template #1
template<class T>
void f(T*); // template #2
template<class T>
void f(const T*); // template #3
void m()
{
const int* p;
f(p); // overload resolution picks: #1: void f(T ) [T = const int *]
// #2: void f(T*) [T = const int]
// #3: void f(const T *) [T = int]
// partial ordering:
// #1 from transformed #2: void(T) from void(U1*): P=T A=U1*: deduction ok: T=U1*
// #2 from transformed #1: void(T*) from void(U1): P=T* A=U1: deduction fails
// #2 is more specialized than #1 with regards to T
// #1 from transformed #3: void(T) from void(const U1*): P=T, A=const U1*: ok
// #3 from transformed #1: void(const T*) from void(U1): P=const T*, A=U1: fails
// #3 is more specialized than #1 with regards to T
// #2 from transformed #3: void(T*) from void(const U1*): P=T* A=const U1*: ok
// #3 from transformed #2: void(const T*) from void(U1*): P=const T* A=U1*: fails
// #3 is more specialized than #2 with regards to T
// result: #3 is selected
// in other words, f(const T*) is more specialized than f(T) or f(T*)
}template<class T>
void f(T, T*); // #1
template<class T>
void f(T, int*); // #2
void m(int* p)
{
f(0, p); // deduction for #1: void f(T, T*) [T = int]
// deduction for #2: void f(T, int*) [T = int]
// partial ordering:
// #1 from #2: void(T,T*) from void(U1,int*): P1=T, A1=U1: T=U1
// P2=T*, A2=int*: T=int: fails
// #2 from #1: void(T,int*) from void(U1,U2*): P1=T A1=U1: T=U1
// P2=int* A2=U2*: fails
// neither is more specialized w.r.t T, the call is ambiguous
}template<class T>
void g(T); // template #1
template<class T>
void g(T&); // template #2
void m()
{
float x;
g(x); // deduction from #1: void g(T ) [T = float]
// deduction from #2: void g(T&) [T = float]
// partial ordering:
// #1 from #2: void(T) from void(U1&): P=T, A=U1 (after adjustment), ok
// #2 from #1: void(T&) from void(U1): P=T (after adjustment), A=U1: ok
// neither is more specialized w.r.t T, the call is ambiguous
}template<class T>
struct A { A(); };
template<class T>
void h(const T&); // #1
template<class T>
void h(A<T>&); // #2
void m()
{
A<int> z;
h(z); // deduction from #1: void h(const T &) [T = A<int>]
// deduction from #2: void h(A<T> &) [T = int]
// partial ordering:
// #1 from #2: void(const T&) from void(A<U1>&): P=T A=A<U1>: ok T=A<U1>
// #2 from #1: void(A<T>&) from void(const U1&): P=A<T> A=const U1: fails
// #2 is more specialized than #1 w.r.t T
const A<int> z2;
h(z2); // deduction from #1: void h(const T&) [T = A<int>]
// deduction from #2: void h(A<T>&) [T = int], but substitution fails
// only one overload to choose from, partial ordering not tried, #1 is called
}Поскольку контекст вызова учитывает только параметры, для которых есть явные аргументы вызова, те пакеты параметров функции,(с C++11) параметры с эллипсисом и параметры с аргументами по умолчанию, для которых нет явного аргумента вызова, игнорируются:
template<class T>
void f(T); // #1
template<class T>
void f(T*, int = 1); // #2
void m(int* ip)
{
int* ip;
f(ip); // calls #2 (T* is more specialized than T)
}template<class T>
void g(T); // #1
template<class T>
void g(T*, ...); // #2
void m(int* ip)
{
g(ip); // calls #2 (T* is more specialized than T)
}template<class T, class U>
struct A {};
template<class T, class U>
void f(U, A<U, T>* p = 0); // #1
template<class U>
void f(U, A<U, U>* p = 0); // #2
void h()
{
f<int>(42, (A<int, int>*)0); // calls #2
f<int>(42); // error: ambiguous
}template<class T>
void g(T, T = T()); // #1
template<class T, class... U>
void g(T, U...); // #2
void h()
{
g(42); // error: ambiguous
}template<class T, class... U>
void f(T, U...); // #1
template<class T>
void f(T); // #2
void h(int i)
{
f(&i); // calls #2 due to the tie-breaker between parameter pack and no parameter
// (note: was ambiguous between DR692 and DR1395)
}template<class T, class... U>
void g(T*, U...); // #1
template<class T>
void g(T); // #2
void h(int i)
{
g(&i); // OK: calls #1 (T* is more specialized than T)
}template<class... T>
int f(T*...); // #1
template<class T>
int f(const T&); // #2
f((int*)0); // OK: selects #2; non-variadic template is more specialized than
// variadic template (was ambiguous before DR1395 because deduction
// failed in both directions)template<class... Args>
void f(Args... args); // #1
template<class T1, class... Args>
void f(T1 a1, Args... args); // #2
template<class T1, class T2>
void f(T1 a1, T2 a2); // #3
f(); // calls #1
f(1, 2, 3); // calls #2
f(1, 2); // calls #3; non-variadic template #3 is more
// specialized than the variadic templates #1 and #2При выведении аргументов шаблона в процессе частичного упорядочения параметры шаблона не требуют сопоставления с аргументами, если аргумент не используется ни в одном из типов, рассматриваемых для частичного упорядочения
template<class T>
T f(int); // #1
template<class T, class U>
T f(U); // #2
void g()
{
f<int>(1); // specialization of #1 is explicit: T f(int) [T = int]
// specialization of #2 is deduced: T f(U) [T = int, U = int]
// partial ordering (only considering the argument type):
// #1 from #2: T(int) from U1(U2): fails
// #2 from #1: T(U) from U1(int): ok: U=int, T unused
// calls #1
}| Частичное упорядочение шаблонов функций, содержащих пакеты параметров шаблонов, не зависит от количества выведенных аргументов для этих пакетов параметров шаблонов. template<class...>
struct Tuple {};
template<class... Types>
void g(Tuple<Types...>); // #1
template<class T1, class... Types>
void g(Tuple<T1, Types...>); // #2
template<class T1, class... Types>
void g(Tuple<T1, Types&...>); // #3
g(Tuple<>()); // calls #1
g(Tuple<int, float>()); // calls #2
g(Tuple<int, float&>()); // calls #3
g(Tuple<int>()); // calls #3 | (с C++11) |
Для компиляции вызова шаблона функции компилятор должен выбрать между перегрузками без шаблонов, перегрузками шаблонов и специализациями перегрузок шаблонов.
template<class T>
void f(T); // #1: template overload
template<class T>
void f(T*); // #2: template overload
void f(double); // #3: non-template overload
template<>
void f(int); // #4: specialization of #1
f('a'); // calls #1
f(new int(1)); // calls #2
f(1.0); // calls #3
f(1); // calls #4Перегрузки функций против специализаций функций
Обратите внимание, что в разрешении перегрузки участвуют только перегрузки без шаблонов и первичные шаблоны перегрузки. Специализации не являются перегрузками и не учитываются. Только после того, как разрешение перегрузки выберет наилучший совпадающий первичный шаблон функции, рассматриваются его специализации, чтобы увидеть, является ли одна из них лучшим соответствием.
template<class T> void f(T); // #1: overload for all types template<> void f(int*); // #2: specialization of #1 for pointers to int template<class T> void f(T*); // #3: overload for all pointer types f(new int(1)); // calls #3, even though specialization of #1 would be a perfect match
Важно помнить это правило при упорядочении заголовочных файлов единицы трансляции. Для получения дополнительных примеров взаимодействия между перегрузками функций и специализациями функций, разверните ниже:
| Примеры |
|---|
| Рассмотрим сначала ситуации, где поиск по аргументам не используется. Для этого мы используем вызов
#include <iostream>
struct A {};
template<class T>
void f(T) { std::cout << "#1\n"; } // overload #1 before f() POR
template<class T>
void f(T*) { std::cout << "#2\n"; } // overload #2 before f() POR
template<class T>
void g(T* t)
{
(f)(t); // f() POR
}
int main()
{
A* p = nullptr;
g(p); // POR of g() and f()
}
// Both #1 and #2 are added to the candidate list;
// #2 is selected because it is a better match.Вывод: #2
#include <iostream>
struct A {};
template<class T>
void f(T) { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t)
{
(f)(t); // f() POR
}
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
int main()
{
A* p = nullptr;
g(p); // POR of g() and f()
}
// Only #1 is added to the candidate list; #2 is defined after POR;
// therefore, it is not considered for overloading even if it is a better match.Вывод: #1
#include <iostream>
struct A {};
template<class T>
void f(T) { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t)
{
(f)(t); // f() POR
}
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
int main()
{
A* p = nullptr;
g(p); // POR of g() and f()
}
// #1 is added to the candidate list; #3 is a better match defined after POR. The
// candidate list consists of #1 which is eventually selected. After that, the explicit
// specialization #3 of #1 declared after POI is selected because it is a better match.
// This behavior is governed by 14.7.3/6 [temp.expl.spec] and has nothing to do with ADL.Вывод: #3
#include <iostream>
struct A {};
template<class T>
void f(T) { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t)
{
(f)(t); // f() POR
}
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
int main()
{
A* p = nullptr;
g(p); // POR of g() and f()
}
// #1 is the only member of the candidate list and it is eventually selected.
// After that, the explicit specialization #3 is skipped because it actually
// specializes #2 declared after POR.Вывод: #1
#include <iostream>
struct A {};
template<class T>
void f(T) { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t)
{
f(t); // f() POR
}
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
int main()
{
A* p = nullptr;
g(p); // POR of g() and f()
}
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected being the better match.Вывод: #2
#include <iostream>
struct A {};
template<class T>
void f(T) { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t)
{
f(t); // f() POR
}
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
int main()
{
A* p = nullptr;
g(p); // POR of g() and f()
}
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected among the primary templates, being the better match.
// Since #3 is declared before #2, it is an explicit specialization of #1.
// Hence the final selection is #2.Вывод: #2
#include <iostream>
struct A {};
template<class T>
void f(T) { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t)
{
f(t); // f() POR
}
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
int main()
{
A* p = nullptr;
g(p); // POR of g() and f()
}
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected among the primary templates, being the better match.
// Since #3 is declared after #2, it is an explicit specialization of #2;
// therefore, selected as the function to call.Вывод: #3
|
Для получения подробных правил по разрешению перегрузки см. разрешение перегрузки.
Специализация шаблона функции
Отчёты о дефектах
Следующие отчёты о дефектах, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применимо к | Поведение, как опубликовано | Правильное поведение |
|---|---|---|---|
| CWG 214 | C++98 | точная процедура частичного упорядочения не была указана | добавленное описание |
| CWG 532 | C++98 | порядок между нестатическим шаблонным членом-функцией и нешаблонной функцией не был указан | добавленное описание |
| CWG 581 | C++98 | список аргументов шаблона в явной специализации или инстанциирования шаблона конструктора был разрешен | запрещено |
| CWG 1321 | C++98 | неясно, эквивалентны ли одинаковые зависимые имена в первом объявлении и повторном объявлении | они эквивалентны и значение такое же, как в первом объявлении |
| CWG 1395 | C++11 | вывод не удался, когда A был из пакета, и не было ни одного пустого пакета для разбора | вывод разрешён, добавлена возможность |
| CWG 1406 | C++11 | тип нового первого параметра, добавленного для нестатического шаблона члена-функции, не был актуален для квалификатора ссылок этого шаблона | тип — ссылка на rvalue, если квалификатор ссылок — && |
| CWG 1446 | C++11 | тип нового первого параметра, добавленного для нестатического шаблона члена-функции без квалификатора ссылок, был типом ссылки на lvalue, даже если этот шаблон члена-функции сравнивается с шаблоном функции, у которого первый параметр имеет тип ссылки на rvalue | в этом случае тип — ссылка на rvalue |
| CWG 2373 | C++98 | новые первые параметры были добавлены к спискам параметров статических шаблонов членов-функций при частичном упорядочении | не добавляются |
См. также
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/function_template