Шаблон функции
Шаблон функции задаёт семейство функций.
Синтаксис
template < parameter-list > function-declaration | (1) | |
template < parameter-list > requires constraint function-declaration | (2) | (начиная с C++20) |
| function-declaration-with-placeholders | (3) | (начиная с C++20) |
export template < parameter-list > function-declaration | (4) | (удалено в C++11) |
Пояснение
| parameter-list | - | непустой список параметров шаблона, разделённых запятыми, каждый из которых является параметром шаблона: параметром, не являющимся типом, параметром типа или параметром шаблона, либо пакетом параметров любого из этих видов(начиная с C++11). Как и у любого шаблона, параметры могут иметь ограничения(начиная с C++20) |
| function-declaration | - | объявление функции. Объявленное имя функции становится именем шаблона. |
| constraint | - | выражение-ограничение, которое ограничивает допустимые параметры шаблона для этого шаблона функции |
|
function-declaration- with-placeholders | - | объявление функции, в котором для типа как минимум одного параметра используется заполнитель 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-head — это список параметров шаблона без имён параметров шаблона и аргументов шаблона по умолчанию, а также без requires-выражения (если оно есть)(начиная с C++20).
Сигнатура шаблона функции включает имя, список типов параметров, возвращаемый тип, завершающее requires-выражение (если оно есть)(начиная с C++20) и сигнатуру template-head. За исключением следующих случаев, сигнатура также включает охватывающее пространство имён.
Если шаблон функции является членом класса, его сигнатура включает класс, членом которого является функция, вместо охватывающего пространства имён. Его сигнатура также включает завершающее requires-выражение (если оно есть)(начиная с C++20), ref-квалификатор (если он есть) и(начиная с C++11) cv-квалификаторы (если они есть).
| Если шаблон функции является дружественной функцией, ограничение которой зависит от параметров охватывающего шаблона, его сигнатура включает охватывающий класс вместо охватывающего пространства имён. | (начиная с C++20) |
Инстанцирование шаблона функции
Шаблон функции сам по себе не является ни типом, ни функцией. Из файла исходного кода, содержащего только определения шаблонов, код не генерируется. Чтобы появился код, шаблон необходимо инстанцировать: аргументы шаблона должны быть определены, чтобы компилятор мог сгенерировать реальную функцию (или класс для шаблона класса).
Явное инстанцирование
template return-type name < argument-list > ( parameter-list ) ; | (1) | |
template return-type name ( parameter-list ) ; | (2) | |
extern template return-type name < argument-list > ( parameter-list ) ; | (3) | (начиная с C++11) |
extern template return-type name ( parameter-list ) ; | (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) |
Объявления явного инстанцирования не подавляют неявное инстанцирование встроенных функций, объявлений auto, ссылок и специализаций шаблонов классов. (Таким образом, когда встроенная функция, являющаяся предметом объявления явного инстанцирования, используется в контексте 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
Два выражения с параметрами шаблона называются эквивалентными, если два определения функций, содержащие эти выражения, были бы одинаковыми согласно правилу одного определения (ODR), то есть оба выражения содержат одну и ту же последовательность токенов, имена в которой разрешаются в одни и те же сущности при поиске имён; при этом имена параметров шаблона могут различаться. Два лямбда-выражения никогда не бывают эквивалентными.(начиная с 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 преобразованного template-1 можно использовать для выведения соответствующего параметра P template-2, но не наоборот, то этот A более специализирован, чем P, в отношении типа (или типов), выведенного этой парой P/A.
Если выведение успешно в обоих направлениях и исходные P и A были ссылочными типами, выполняются дополнительные проверки:
- Если
Aбыл lvalue-ссылкой, аP— rvalue-ссылкой,Aсчитается более специализированным, чемP - Если
Aимел более строгую cv-квалификацию, чемP,Aсчитается более специализированным, чемP
Во всех остальных случаях ни один из шаблонов не является более специализированным, чем другой, в отношении типа (или типов), выведенного этой парой P/A.
После рассмотрения каждой пары P и A в обоих направлениях, если для каждого рассмотренного типа выполняются следующие условия:
- template-1 не менее специализирован, чем template-2, для всех типов;
- template-1 более специализирован, чем template-2, для некоторых типов;
- template-2 не является более специализированным, чем template-1, ни для одного типа ИЛИ не является по меньшей мере столь же специализированным для каких-либо типов,
то template-1 более специализирован, чем template-2. Если приведённые выше условия выполняются после перестановки шаблонов, то template-2 более специализирован, чем template-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
|
Подробные правила разрешения перегрузки см. в разделе разрешение перегрузки.
Специализация шаблона функции
Ключевые слова
template, extern (начиная с C++11)
Отчеты об ошибках
Следующие отчеты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам 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 | тип нового первого параметра, добавленного для шаблона функции нестатического члена, не имел отношения к квалификатору ref этого шаблона | тип является типом ссылки на rvalue, если квалификатор ref — && |
| CWG 1446 | C++11 | тип нового первого параметра, добавленного для шаблона функции нестатического члена без квалификатора ref, был типом ссылки на lvalue, даже если этот шаблон функции-члена сравнивался с шаблоном функции, первый параметр которого имел тип ссылки на rvalue | в этом случае тип является типом ссылки на rvalue |
| CWG 2373 | C++98 | при частичном упорядочении в списки параметров шаблонов функций статических членов добавлялись новые первые параметры | не добавляются |
См. также
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/function_template