Выведение аргументов шаблона
Чтобы инстанцировать шаблон функции, необходимо знать все аргументы шаблона, но указывать каждый из них необязательно. Если возможно, компилятор выведет недостающие аргументы шаблона из аргументов функции. Это происходит при попытке вызова функции, при взятии адреса шаблона функции и в некоторых других контекстах:
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)
// and stores its address in ptr
}
Этот механизм позволяет использовать шаблонные операторы, поскольку для указания аргументов шаблона оператору нет синтаксиса, кроме как переписать его в виде выражения вызова функции:
#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>>
}
Выведение аргументов шаблона происходит после поиска имени шаблона функции (который может включать поиск, зависящий от аргументов) и до подстановки аргументов шаблона (которая может включать SFINAE) и разрешения перегрузки.
| Выведение аргументов шаблона также выполняется, когда имя шаблона класса используется как тип создаваемого объекта: std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);
std::copy_n(vi1, 3, std::back_insert_iterator(vi2));
std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...}));
auto lck = std::lock_guard(foo.mtx);
std::lock_guard lck2(foo.mtx, ul);
Выведение аргументов шаблона для шаблонов классов происходит в объявлениях и явных выражениях приведения; подробности см. в разделе выведение аргументов шаблона класса. | (начиная с C++17) |
Выведение из вызова функции
При выведении аргументов шаблона предпринимается попытка определить аргументы шаблона (типы для параметров шаблона типов Ti, шаблоны для параметров шаблона-шаблонов TTi и значения для нетиповых параметров шаблона Ii), которые можно подставить в каждый тип параметра функции P, чтобы получить выведенный тип A, совпадающий с типом аргумента A с учетом приведенных ниже преобразований.
Если параметров несколько, каждая пара P/A обрабатывается отдельно, после чего выведенные аргументы шаблона объединяются. Если выведение для какой-либо пары P/A завершается неудачей или неоднозначно, если для разных пар получены разные выведенные аргументы шаблона либо если какой-либо аргумент шаблона остался невыведенным и не был указан явно, компиляция завершается ошибкой.
| Если после удаления ссылок и cv-квалификаторов из template<class T>
void f(std::initializer_list<T>);
f({1, 2, 3}); // P = std::initializer_list<T>, A = {1, 2, 3}
// P'1 = T, A'1 = 1: deduced T = int
// P'2 = T, A'2 = 2: deduced T = int
// P'3 = T, A'3 = 3: deduced T = int
// OK: deduced T = int
f({1, "abc"}); // P = std::initializer_list<T>, A = {1, "abc"}
// P'1 = T, A'1 = 1: deduced T = int
// P'2 = T, A'2 = "abc": deduced T = const char*
// error: deduction fails, T is ambiguous
Если после удаления ссылок и cv-квалификаторов из template<class T, int N>
void h(T const(&)[N]);
h({1, 2, 3}); // deduced T = int, deduced N = 3
template<class T>
void j(T const(&)[3]);
j({42}); // deduced T = int, array bound is not a parameter, not considered
struct Aggr
{
int i;
int j;
};
template<int N>
void k(Aggr const(&)[N]);
k({1, 2, 3}); // error: deduction fails, no conversion from int to Aggr
k({{1}, {2}, {3}}); // OK: deduced N = 3
template<int M, int N>
void m(int const(&)[M][N]);
m({{1, 2}, {3, 4}}); // deduced M = 2, deduced N = 2
template<class T, int N>
void n(T const(&)[N], T);
n({{1}, {2}, {3}}, Aggr()); // deduced T = Aggr, deduced N = 3
Если пакет параметров появляется в последней позиции template<class... Types>
void f(Types&...);
void h(int x, float& y)
{
const int z = x;
f(x, y, z); // P = Types&..., A1 = x: deduced first member of Types... = int
// P = Types&..., A2 = y: deduced second member of Types... = float
// P = Types&..., A3 = z: deduced third member of Types... = const int
// calls f<int, float, const int>
}
| (начиная с C++11) |
Если P является типом функции, указателем на тип функции или указателем на функцию-член, а A — набором перегруженных функций, не содержащим шаблонов функций, выведение аргументов шаблона выполняется для каждой перегрузки. Если успешно только одно выведение, используется его результат. Если не удалось ни одно выведение или успешно более одного, параметр шаблона относится к невыводимому контексту (см. ниже):
template<class T>
int f(T(*p)(T));
int g(int);
int g(char);
f(g); // P = T(*)(T), A = overload set
// P = T(*)(T), A1 = int(int): deduced T = int
// P = T(*)(T), A2 = int(char): fails to deduce T
// only one overload works, deduction succeeds
Перед началом выведения к P и A применяются следующие преобразования:
P не является ссылочным типом,A является типом массива, A заменяется типом указателя, полученным в результате преобразования массива в указатель;A является типом функции, A заменяется типом указателя, полученным в результате преобразования функции в указатель;A является cv-квалифицированным типом, при выведении игнорируются cv-квалификаторы верхнего уровня: template<class T> void f(T); int a[3]; f(a); // P = T, A = int[3], adjusted to int*: deduced T = int* void b(int); f(b); // P = T, A = void(int), adjusted to void(*)(int): deduced T = void(*)(int) const int c = 13; f(c); // P = T, A = const int, adjusted to int: deduced T = int
P является cv-квалифицированным типом, при выведении игнорируются cv-квалификаторы верхнего уровня.P является ссылочным типом, при выведении используется тип, на который он ссылается.P является rvalue-ссылкой на cv-неквалифицированный параметр шаблона (так называемая универсальная ссылка), а соответствующий аргумент вызова функции является lvalue, для выведения вместо A используется тип lvalue-ссылки на A (Примечание: на этом основано действие std::forward. Примечание: при выведении аргументов шаблона класса параметр шаблона класса никогда не является универсальной ссылкой(начиная с C++17)): template<class T>
int f(T&&); // P is an rvalue reference to cv-unqualified T (forwarding reference)
template<class T>
int g(const T&&); // P is an rvalue reference to cv-qualified T (not special)
int main()
{
int i;
int n1 = f(i); // argument is lvalue: calls f<int&>(int&) (special case)
int n2 = f(0); // argument is not lvalue: calls f<int>(int&&)
// int n3 = g(i); // error: deduces to g<int>(const int&&), which
// cannot bind an rvalue reference to an lvalue
}
После этих преобразований выведение выполняется описанным ниже способом (см. раздел выведение из типа): предпринимается попытка найти такие аргументы шаблона, при которых выведенный A (то есть P после указанных выше преобразований и подстановки выведенных параметров шаблона) был бы идентичен преобразованному A, то есть A после указанных выше преобразований.
Если обычное выведение из P и A завершается неудачей, дополнительно рассматриваются следующие варианты:
P является ссылочным типом, выведенный A (то есть тип, на который ссылается ссылка) может иметь больше cv-квалификаторов, чем преобразованный A: template<typename T>
void f(const T& t);
bool a = false;
f(a); // P = const T&, adjusted to const T, A = bool:
// deduced T = bool, deduced A = const bool
// deduced A is more cv-qualified than A
A может быть другим указателем или указателем на член, который можно преобразовать в выведенный A с помощью преобразования квалификации или преобразования указателя на функцию(начиная с C++17): template<typename T>
void f(const T*);
int* p;
f(p); // P = const T*, A = int*:
// deduced T = int, deduced A = const int*
// qualification conversion applies (from int* to const int*)
P является классом, а P имеет форму простого идентификатора шаблона (имени шаблона со списком аргументов шаблона) или спецификатора splice со списком аргументов шаблона (splice specifier)(начиная с C++26), преобразованный A может быть производным классом выведенного A. Аналогично, если P является указателем на класс вида простого идентификатора шаблона (имени шаблона со списком аргументов шаблона) или спецификатора splice со списком аргументов шаблона (splice specifier)(начиная с C++26), преобразованный A может быть указателем на производный класс, на который указывает выведенный A: template<class T>
struct B {};
template<class T>
struct D : public B<T> {};
template<class T>
void f(B<T>&) {}
void f()
{
D<int> d;
f(d); // P = B<T>&, adjusted to P = B<T> (a simple-template-id), A = D<int>:
// deduced T = int, deduced A = B<int>
// A is derived from deduced A
}
Невыводимые контексты
В следующих случаях типы, шаблоны и нетиповые значения, используемые для составления P, не участвуют в выведении аргументов шаблона, а вместо этого используют аргументы шаблона, выведенные в других местах или указанные явно. Если параметр шаблона используется только в невыводимых контекстах и не указан явно, выведение аргументов шаблона завершается неудачей.
::) типа, заданного с помощью квалифицированного идентификатора: // the identity template, often used to exclude specific arguments from deduction
// (available as std::type_identity as of C++20)
template<typename T>
struct identity { typedef T type; };
template<typename T>
void bad(std::vector<T> x, T value = 1);
template<typename T>
void good(std::vector<T> x, typename identity<T>::type value = 1);
std::vector<std::complex<double>> x;
bad(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>>
// P1/A1: deduced T = std::complex<double>
// P2 = T, A2 = double
// P2/A2: deduced T = double
// error: deduction fails, T is ambiguous
good(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>>
// P1/A1: deduced T = std::complex<double>
// P2 = identity<T>::type, A2 = double
// P2/A2: uses T deduced by P1/A1 because T is to the left of :: in P2
// OK: T = std::complex<double>
2) спецификатор индексации пакета или выражение индексации пакета: template<typename... Ts> void f(Ts...[0], std::tuple<Ts...>); f(3, std::tuple(5, 'A')); // P2 = std::tuple<Ts...>, A2 = std::tuple<int, char> // P2/A2: deduced first member of Ts... = int // P2/A2: deduced second member of Ts... = char // P1 = Ts...[0], A1 = int: Ts...[0] is in non-deduced context | (начиная с C++26) |
3) Выражение в спецификаторе decltype: template<typename T> void f(decltype(*std::declval<T>()) arg); int n; f<int*>(n); // P = decltype(*declval<T>()), A = int: T is in non-deduced context | (начиная с C++11) |
4) Выражение в спецификаторе splice: template<std::meta::info R> void f(typename[:R:] arg); int n; f<^^int>(n); // P = typename[:R:], A = ^^int: R is in non-deduced context | (начиная с C++26) |
template<std::size_t N>
void f(std::array<int, 2 * N> a);
std::array<int, 10> a;
f(a); // P = std::array<int, 2 * N>, A = std::array<int, 10>:
// 2 * N is non-deduced context, N cannot be deduced
// note: f(std::array<int, N> a) would be able to deduce N
template<typename T, typename F>
void f(const std::vector<T>& v, const F& comp = std::less<T>());
std::vector<std::string> v(3);
f(v); // P1 = const std::vector<T>&, A1 = std::vector<std::string> lvalue
// P1/A1 deduced T = std::string
// P2 = const F&, A2 = std::less<std::string> rvalue
// P2 is non-deduced context for F (template parameter) used in the
// parameter type (const F&) of the function parameter comp,
// that has a default argument that is being used in the call f(v)
P, A которого является функцией или набором перегрузок, в котором несколько функций соответствуют P либо ни одна функция не соответствует P, или набор перегрузок включает один или несколько шаблонов функций: template<typename T>
void out(const T& value) { std::cout << value; }
out("123"); // P = const T&, A = const char[4] lvalue: deduced T = char[4]
out(std::endl); // P = const T&, A = function template: T is in non-deduced context
P, A которого является списком инициализации в фигурных скобках, но P не является std::initializer_list, ссылкой на него (возможно, cv-квалифицированную) или ссылкой на массив: template<class T>
void g1(std::vector<T>);
template<class T>
void g2(std::vector<T>, T x);
g1({1, 2, 3}); // P = std::vector<T>, A = {1, 2, 3}: T is in non-deduced context
// error: T is not explicitly specified or deduced from another P/A
g2({1, 2, 3}, 10); // P1 = std::vector<T>, A1 = {1, 2, 3}: T is in non-deduced context
// P2 = T, A2 = int: deduced T = int
9) Параметр P, являющийся пакетом параметров и не стоящий в конце списка параметров: template<class... Ts, class T>
void f1(T n, Ts... args);
template<class... Ts, class T>
void f2(Ts... args, T n);
f1(1, 2, 3, 4); // P1 = T, A1 = 1: deduced T = int
// P2 = Ts..., A2 = 2, A3 = 3, A4 = 4: deduced Ts = [int, int, int]
f2(1, 2, 3, 4); // P1 = Ts...: Ts is non-deduced context
10) Список параметров шаблона внутри параметра P, содержащий раскрытие пакета, которое находится не в самом конце списка параметров шаблона: template<int...>
struct T {};
template<int... Ts1, int N, int... Ts2>
void good(const T<N, Ts1...>& arg1, const T<N, Ts2...>&);
template<int... Ts1, int N, int... Ts2>
void bad(const T<Ts1..., N>& arg1, const T<Ts2..., N>&);
T<1, 2> t1;
T<1, -1, 0> t2;
good(t1, t2); // P1 = const T<N, Ts1...>&, A1 = T<1, 2>:
// deduced N = 1, deduced Ts1 = [2]
// P2 = const T<N, Ts2...>&, A2 = T<1, -1, 0>:
// deduced N = 1, deduced Ts2 = [-1, 0]
bad(t1, t2); // P1 = const T<Ts1..., N>&, A1 = T<1, 2>:
// <Ts1..., N> is non-deduced context
// P2 = const T<Ts2..., N>&, A2 = T<1, -1, 0>:
// <Ts2..., N> is non-deduced context
| (начиная с C++11) |
P типа массива (но не ссылки на массив и не указателя на массив) — старшая граница массива: template<int i>
void f1(int a[10][i]);
template<int i>
void f2(int a[i][20]); // P = int[i][20], array type
template<int i>
void f3(int (&a)[i][20]); // P = int(&)[i][20], reference to array
void g()
{
int a[10][20];
f1(a); // OK: deduced i = 20
f1<20>(a); // OK
f2(a); // error: i is non-deduced context
f2<10>(a); // OK
f3(a); // OK: deduced i = 10
f3<10>(a); // OK
}
В любом случае, если какая-либо часть имени типа невыводима, всё имя типа является невыводимым контекстом. Однако составные типы могут включать как выводимые, так и невыводимые имена типов. Например, в A<T>::B<T2>, T является невыводимым из-за правила № 1 (вложенный спецификатор имени), а T2 невыводим, поскольку входит в то же имя типа; но в void(*f)(typename A<T>::B, A<T>), T в A<T>::B невыводим (по той же причине), тогда как T в A<T> выводим.
Выведение из типа
Для параметра функции P, зависящего от одного или нескольких параметров шаблона типов Ti, параметров шаблона-шаблонов TTi или нетиповых параметров шаблона Ii, и соответствующего аргумента A выведение выполняется, если P имеет одну из следующих форм:
-
cv(необязательно)T; -
T*; -
T&;
| (начиная с C++11) |
-
T(необязательно)[I(необязательно)];
| (до C++17) |
| (начиная с C++17) |
-
T(необязательно)U(необязательно)::*; -
TT(необязательно)<T>; -
TT(необязательно)<I>; -
TT(необязательно)<TU>; -
TT(необязательно)<>.
В приведённых выше формах:
-
T(необязательно) илиU(необязательно) обозначает тип или список типов параметров, который либо рекурсивно удовлетворяет этим правилам, либо является невыводимым контекстом вPилиA, либо представляет собой один и тот же независящий тип вPиA. -
TT(необязательно) илиTU(необязательно) обозначает шаблон класса или параметр шаблона-шаблона. -
I(необязательно) обозначает выражение, которое либо являетсяI, либо зависит от значения вPилиA, либо имеет одинаковое постоянное значение вPиA.
| (начиная с C++17) |
Если P имеет одну из форм, включающих список параметров шаблона <T> или <I>, каждый элемент Pi этого списка аргументов шаблона сопоставляется с соответствующим аргументом шаблона Ai его A. Если последний Pi является раскрытием пакета, его шаблон сопоставляется с каждым оставшимся аргументом в списке аргументов шаблона A. Завершающий пакет параметров, который иначе не выводится, выводится как пустой пакет параметров.
Если P имеет одну из форм, включающих список параметров функции (T), каждый параметр Pi из этого списка сопоставляется с соответствующим аргументом Ai из списка параметров функции A. Если последний Pi является раскрытием пакета, его декларативная часть сопоставляется с каждым оставшимся Ai в списке типов параметров A.
Формы могут быть вложенными и обрабатываться рекурсивно:
-
X<int>(*)(char[6])— примерT*, гдеT— этоX<int>(char[6]);
| (до C++17) |
| (начиная с C++17) |
-
X<int>— примерTT(необязательно)<T>, гдеTT— этоX, аT— int, и -
char[6]— примерT(необязательно)[I(необязательно)], гдеT— char, аI—std::size_t(6).
| Аргумент шаблона типа нельзя вывести из типа нетипового аргумента шаблона: template<typename T, T i>
void f(double a[10][i]);
double v[10][20];
f(v); // P = double[10][i], A = double[10][20]:
// i can be deduced to equal 20
// but T cannot be deduced from the type of i
| (до C++17) |
| Если значение аргумента, соответствующего нетиповому параметру шаблона P, объявленному с зависимым типом, выводится из выражения, параметры шаблона в типе P выводятся из типа этого значения. template<long n>
struct A {};
template<class T>
struct C;
template<class T, T n>
struct C<A<n>> { using Q = T; };
typedef long R;
typedef C<A<2>>::Q R; // OK: T was deduced to long
// from the template argument value in the type A<2>
template<auto X>
class bar {};
template<class T, T n>
void f(bar<n> x);
f(bar<3>{}); // OK: T was deduced to int (and n to 3)
// from the template argument value in the type bar<3>
Тип template<class T, T i> void f(int (&a)[i]); int v[10]; f(v); // OK: T is std::size_t Тип template<bool>
struct A {};
template<auto>
struct B;
template<auto X, void (*F)() noexcept(X)>
struct B<F> { A<X> ax; };
void f_nothrow() noexcept;
B<f_nothrow> bn; // OK: X is deduced as true and the type of X is deduced as bool.
| (начиная с C++17) |
Если нетиповый параметр шаблона функции используется в списке параметров шаблона параметра функции (который также является шаблоном), а соответствующий аргумент шаблона выведен, тип выведенного аргумента шаблона (как задано в окружающем списке параметров шаблона, то есть ссылки сохраняются) должен точно соответствовать типу нетипового параметра шаблона, за исключением того, что cv-квалификаторы отбрасываются. Исключение также составляет случай, когда аргумент шаблона выводится из границы массива: тогда допустим любой целочисленный тип, в том числе bool, хотя он всегда становился бы true:
template<int i>
class A {};
template<short s>
void f(A<s>); // the type of the non-type template param is short
void k1()
{
A<1> a; // the type of the non-type template param of a is int
f(a); // P = A<(short)s>, A = A<(int)1>
// error: deduced non-type template argument does not have the same
// type as its corresponding template argument
f<1>(a); // OK: the template argument is not deduced,
// this calls f<(short)1>(A<(short)1>)
}
template<int&>
struct X;
template<int& R>
void k2(X<R>&);
int n;
void g(X<n> &x)
{
k2(x); // P = X<R>, A = X<n>
// parameter type is int&
// argument type is int& in struct X's template declaration
// OK (with CWG 2091): deduces R to refer to n
}
Параметр шаблона типа нельзя вывести из типа аргумента функции по умолчанию:
template<typename T>
void f(T = 5, T = 7);
void g()
{
f(1); // OK: calls f<int>(1, 7)
f(); // error: cannot deduce T
f<int>(); // OK: calls f<int>(5, 7)
}
Для выведения параметра шаблона-шаблона можно использовать тип, заданный в специализации шаблона, использованной в вызове функции:
template<template<typename> class X>
struct A {}; // A is a template with a TT param
template<template<typename> class TT>
void f(A<TT>) {}
template<class T>
struct B {};
A<B> ab;
f(ab); // P = A<TT>, A = A<B>: deduced TT = B, calls f(A<B>)
Другие контексты
Помимо вызовов функций и выражений с операторами, выведение аргументов шаблона используется в следующих ситуациях:
Выведение типа autoВыведение аргументов шаблона используется в объявлениях переменных при определении значения спецификатора auto по инициализатору переменной. Параметр const auto& x = 1 + 2; // P = const U&, A = 1 + 2:
// same rules as for calling f(1 + 2) where f is
// template<class U> void f(const U& u)
// deduced U = int, the type of x is const int&
auto l = {13}; // P = std::initializer_list<U>, A = {13}:
// deduced U = int, the type of l is std::initializer_list<int>
При прямой инициализации списком (но не при копирующей инициализации списком), если значение auto выводится из списка инициализации в фигурных скобках, список должен содержать только один элемент, а типом auto будет тип этого элемента: auto x1 = {3}; // x1 is std::initializer_list<int>
auto x2{1, 2}; // error: not a single element
auto x3{3}; // x3 is int
// (before N3922 x2 and x3 were both std::initializer_list<int>)
| (начиная с C++11) |
Функции с возвращаемым типом autoВыведение аргументов шаблона используется в объявлениях функций при определении значения спецификатора auto в типе возвращаемого значения функции по оператору return. Для функций с возвращаемым типом auto параметр auto f() { return 42; } // P = auto, A = 42:
// deduced U = int, the return type of f is int
Если у такой функции несколько операторов return, выведение выполняется для каждого из них. Все полученные типы должны совпадать; этот тип становится фактическим типом возвращаемого значения. Если у такой функции нет оператора return, при выведении Примечание: значение заполнителя | (начиная с C++14) |
Разрешение перегрузки
Выведение аргументов шаблона используется при разрешении перегрузки для создания специализаций из функции-шаблона-кандидата. P и A совпадают с соответствующими значениями при обычном вызове функции:
std::string s; std::getline(std::cin, s); // "std::getline" names 4 function templates, // 2 of which are candidate functions (correct number of parameters) // 1st candidate template: // P1 = std::basic_istream<CharT, Traits>&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // deduction determines the type template parameters CharT, Traits, and Allocator // specialization std::getline<char, std::char_traits<char>, std::allocator<char>> // 2nd candidate template: // P1 = std::basic_istream<CharT, Traits>&&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // deduction determines the type template parameters CharT, Traits, and Allocator // specialization std::getline<char, std::char_traits<char>, std::allocator<char>> // overload resolution ranks reference binding from lvalue std::cin // and picks the first of the two candidate specializations
Если выведение завершается неудачей или завершается успешно, но полученная специализация недопустима (например, параметры перегруженного оператора не являются ни типами класса, ни типами перечисления), специализация не включается в набор перегрузок, как и в случае SFINAE.
Адрес набора перегрузок
Выведение аргументов шаблона используется при взятии адреса набора перегрузок, включающего шаблоны функций.
Тип функции-шаблона — P. Целевой тип — это тип A:
std::cout << std::endl; // std::endl names a function template // type of endl P = // std::basic_ostream<CharT, Traits>& (std::basic_ostream<CharT, Traits>&) // operator<< parameter A = // std::basic_ostream<char, std::char_traits<char>>& (*)( // std::basic_ostream<char, std::char_traits<char>>& // ) // (other overloads of operator<< are not viable) // deduction determines the type template parameters CharT and Traits
В этом случае к выведению применяется дополнительное правило: при сравнении параметров функций Pi и Ai, если любой Pi является rvalue-ссылкой на cv-неквалифицированный параметр шаблона («универсальной ссылкой»), а соответствующий Ai является lvalue-ссылкой, то Pi преобразуется в тип параметра шаблона (T&& становится T).
| Если тип возвращаемого значения шаблона функции является заполнителем (auto или | (начиная с C++14) |
Частичное упорядочение
Выведение аргументов шаблона используется при частичном упорядочении перегруженных шаблонов функций.
Шаблон функции преобразования
Выведение аргументов шаблона используется при выборе аргументов шаблона пользовательской функции преобразования.
A — это тип, требуемый в качестве результата преобразования. P — тип возвращаемого значения шаблона функции преобразования. Если P является ссылочным типом, для следующих частей раздела вместо P используется тип, на который ссылается ссылка.
Если A не является ссылочным типом:
P является типом массива, вместо P используется тип указателя, полученный преобразованием массива в указатель;P является типом функции, вместо P используется тип указателя на функцию, полученный преобразованием функции в указатель;P является cv-квалифицированным, cv-квалификаторы верхнего уровня игнорируются.Если A является cv-квалифицированным, cv-квалификаторы верхнего уровня игнорируются. Если A является ссылочным типом, при выведении используется тип, на который он ссылается.
Если обычное выведение из P и A (описанное выше) завершается неудачей, дополнительно рассматриваются следующие варианты:
A является ссылочным типом, A может иметь больше cv-квалификаторов, чем выведенный A;A является указателем или указателем на член, выведенный A может быть любым указателем, который можно преобразовать в A с помощью преобразования квалификации: struct C
{
template<class T>
operator T***();
};
C c;
const int* const* const* p1 = c;
// P = T***, A = const int* const* const*
// regular function-call deduction for
// template<class T> void f(T*** p) as if called with the argument
// of type const int* const* const* fails
// additional deduction for conversion functions determines T = int
// (deduced A is int***, convertible to const int* const* const*)
c) если A является типом указателя на функцию, выведенный A может быть указателем на функцию noexcept, преобразуемым в A с помощью преобразования указателя на функцию; d) если A является указателем на функцию-член, выведенный A может быть указателем на функцию-член noexcept, преобразуемым в A с помощью преобразования указателя на функцию. | (начиная с C++17) |
Другие правила, касающиеся шаблонов функций преобразования, см. в разделе шаблон члена класса.
Явное инстанцирование
Выведение аргументов шаблона используется в явных инстанцированиях, явных специализациях и тех объявлениях friend, в которых declarator-id ссылается на специализацию шаблона функции (например, friend ostream& operator<< <> (...)). Если не все аргументы шаблона заданы явно или имеют значения по умолчанию, для определения того, на специализацию какого шаблона ссылается объявление, используется выведение аргументов шаблона.
P — это тип шаблона функции, рассматриваемого в качестве потенциального совпадения, а A — тип функции из объявления. Если совпадений нет или их больше одного (после частичного упорядочения), объявление функции некорректно:
template<class X>
void f(X a); // 1st template f
template<class X>
void f(X* a); // 2nd template f
template<>
void f<>(int* a) {} // explicit specialization of f
// P1 = void(X), A1 = void(int*): deduced X = int*, f<int*>(int*)
// P2 = void(X*), A2 = void(int*): deduced X = int, f<int>(int*)
// f<int*>(int*) and f<int>(int*) are then submitted to partial ordering
// which selects f<int>(int*) as the more specialized template
В этом случае к выведению применяется дополнительное правило: при сравнении параметров функции Pi и Ai, если какой-либо Pi является rvalue-ссылкой на cv-неквалифицированный параметр шаблона («универсальная ссылка»), а соответствующий Ai является lvalue-ссылкой, то Pi приводится к типу параметра шаблона (T&& становится T).
Шаблон функции освобождения памяти
Выведение аргументов шаблона используется при определении того, соответствует ли специализация шаблона функции освобождения памяти заданной форме размещения operator new.
P — это тип шаблона функции, рассматриваемого в качестве потенциального совпадения, а A — тип функции освобождения памяти, которая соответствовала бы рассматриваемому оператору размещения new. Если совпадений нет или их больше одного (после разрешения перегрузки), функция освобождения памяти для размещения не вызывается (может произойти утечка памяти):
struct X
{
X() { throw std::runtime_error(""); }
static void* operator new(std::size_t sz, bool b) { return ::operator new(sz); }
static void* operator new(std::size_t sz, double f) { return ::operator new(sz); }
template<typename T>
static void operator delete(void* ptr, T arg)
{
::operator delete(ptr);
}
};
int main()
{
try
{
X* p1 = new (true) X; // when X() throws, operator delete is looked up
// P1 = void(void*, T), A1 = void(void*, bool):
// deduced T = bool
// P2 = void(void*, T), A2 = void(void*, double):
// deduced T = double
// overload resolution picks operator delete<bool>
}
catch(const std::exception&) {}
try
{
X* p1 = new (13.2) X; // same lookup, picks operator delete<double>
}
catch(const std::exception&) {}
}
Шаблоны псевдонимов
Шаблоны псевдонимов не участвуют в выведении, за исключением выведения аргументов шаблона класса(начиная с C++20):
template<class T>
struct Alloc {};
template<class T>
using Vec = vector<T, Alloc<T>>;
Vec<int> v;
template<template<class, class> class TT>
void g(TT<int, Alloc<int>>);
g(v); // OK: deduced TT = vector
template<template<class> class TT>
void f(TT<int>);
f(v); // error: TT cannot be deduced as "Vec" because Vec is an alias template
Неявные преобразования
При выведении типа не учитываются неявные преобразования (кроме перечисленных выше корректировок типов): это задача разрешения перегрузки, которое происходит позже. Однако если выведение успешно для всех параметров, участвующих в выведении аргументов шаблона, а все невыведенные аргументы шаблона заданы явно или имеют значения по умолчанию, то оставшиеся параметры функции сравниваются с соответствующими аргументами функции. Для каждого оставшегося параметра P, тип которого до подстановки любых явно заданных аргументов шаблона не зависел от параметров шаблона, если соответствующий аргумент A нельзя неявно преобразовать к P, выведение завершается неудачей.
Параметры с зависимыми типами, в которых ни один параметр шаблона не участвует в выведении аргументов шаблона, а также параметры, ставшие независимыми после подстановки явно заданных аргументов шаблона, проверяются во время разрешения перегрузки:
template<class T>
struct Z { typedef typename T::x xx; };
template<class T>
typename Z<T>::xx f(void*, T); // #1
template<class T>
void f(int, T); // #2
struct A {} a;
int main()
{
f(1, a); // for #1, deduction determines T = struct A, but the remaining argument 1
// cannot be implicitly converted to its parameter void*: deduction fails
// instantiation of the return type is not requested
// for #2, deduction determines T = struct A, and the remaining argument 1
// can be implicitly converted to its parameter int: deduction succeeds
// the function call compiles as a call to #2 (deduction failure is SFINAE)
}
Отчёты об ошибках стандарта
Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.
| DR | Применён к | Поведение согласно опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 70 | C++98 | не было указано, выводятся ли границы массива | указано, что они не выводятся |
| CWG 300 | C++98 | выведение выполнялось для параметров функции видаtype(*)(T)/T(*)()/T(*)(T); указатели на функциисоответствуют этим формам, а ссылки на функции — нет | изменить эти формы наtype(T)/T()/T(T), чтобы онитакже охватывали ссылки |
| CWG 322 | C++98 | параметры типа, являющиеся ссылочными типами, не приводились к ссылочному типу для выведения | добавлена корректировка |
| CWG 976 | C++98 | при выведении для шаблонов операторов преобразования тип возвращаемого значения const T& никогда не мог совпасть с типом результата T | правила скорректированы, чтобы разрешить такие совпадения |
| CWG 1387 | C++11 | выражение в спецификаторе decltype не считалось контекстом, в котором выведение не выполняется | теперь считается |
| CWG 1391 | C++98 | не было указано влияние неявных преобразований аргументов, не участвующих в выведении | указано, как описано выше |
| CWG 1591 | C++11 | нельзя вывести границу массива и тип элемента из списка инициализации в фигурных скобках | выведение разрешено |
| CWG 2052 | C++98 | выведение для оператора с аргументами, не являющимися ни классами, ни перечислениями, приводило к неустранимой ошибке | неустранимая ошибка, если нет других перегрузок |
| CWG 2091 | C++98 | выведение ссылочного параметра, не являющегося типом, не работало из-за несовпадения типа с аргументом | несовпадение типов устранено |
| N3922 | C++11 | прямая инициализация списком для auto выводит std::initializer_list | некорректна при наличии более одного элемента; для одного элемента выводится тип элемента |
| CWG 2355 | C++17 | значение в спецификаторе noexcept типа функции не выводилось | сделано выводимым |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/template_argument_deduction