Namespaces
Variants

Template argument deduction

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Для создания экземпляра шаблона функции должны быть известны все аргументы шаблона, но не все аргументы шаблона обязательно указывать явно. Когда это возможно, компилятор выводит недостающие аргументы шаблона из аргументов функции. Это происходит при попытке вызова функции, при взятии адреса шаблона функции и в некоторых других контекстах :

template<typename To, typename From>
To convert(From f);
void g(double d)
{
    int i = convert<int>(d);    // вызывает convert<int, double>(double)
    char c = convert<char>(d);  // вызывает convert<char, double>(double)
    int(*ptr)(float) = convert; // инстанцирует convert<int, float>(float)
                                // и сохраняет его адрес в ptr
}

Этот механизм позволяет использовать шаблонные операторы, поскольку нет синтаксиса для указания шаблонных аргументов для оператора, кроме как переписав его в виде выражения вызова функции:

#include <iostream>
int main()
{
    std::cout << "Hello, world" << std::endl;
    // operator<< ищется через ADL как std::operator<<,
    // затем выводится как operator<<<char, std::char_traits<char>> оба раза
    // std::endl выводится как &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)

Содержание

Вывод из вызова функции

Вывод аргументов шаблона пытается определить аргументы шаблона (типы для параметров шаблона типа T i, шаблоны для параметров шаблона-шаблона TT i и значения для параметров шаблона-константы I i), которые могут быть подставлены в каждый параметр P для получения типа выведенного A , который совпадает с типом аргумента A после выполнения перечисленных ниже корректировок.

Если имеется несколько параметров, каждая пара P / A выводится отдельно, и выведенные аргументы шаблона затем объединяются. Если вывод завершается неудачей или является неоднозначным для любой пары P / A , или если разные пары дают различные выведенные аргументы шаблона, или если какой-либо аргумент шаблона остается ни выведенным, ни явно указанным, компиляция завершается неудачей.

Если удаление ссылок и cv-квалификаторов из P дает std:: initializer_list < P '> и A является braced-init-list , тогда вывод выполняется для каждого элемента списка инициализации, принимая P' в качестве параметра и элемент списка A' в качестве аргумента:

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-квалификаторов из P дает P' [ N ], и A является непустым braced-init-list, тогда вывод выполняется как выше, за исключением того, что если N является константным параметром шаблона, он выводится из длины списка инициализации:

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

Если parameter pack появляется как последний P , тогда тип P сопоставляется с типом A каждого оставшегося аргумента вызова. Каждое сопоставление выводит аргументы шаблона для следующей позиции в раскрытии пака:

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 = набор перегруженных функций
      // P = T(*)(T), A1 = int(int): выведено T = int
      // P = T(*)(T), A2 = int(char): не удалось вывести T
      // работает только одна перегрузка, вывод успешен

Перед началом вывода производятся следующие корректировки P и A :

1) Если P не является ссылочным типом,
а) если A является типом массива, A заменяется на тип указателя, полученный в результате преобразования массива в указатель;
б) в противном случае, если A является типом функции, A заменяется на указательный тип, полученный в результате преобразования функции в указатель;
c) в противном случае, если 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
2) Если P является cv-квалифицированным типом, то топ-уровневые cv-квалификаторы игнорируются при выводе.
3) Если P является ссылочным типом, для вывода используется тип, на который ссылаются.
4) Если P является rvalue-ссылкой на неквалифицированный шаблонный параметр (так называемые forwarding references ), и соответствующий аргумент вызова функции является lvalue, то тип lvalue-ссылки на A используется вместо A для вывода (Примечание: это основа для действия std::forward . Примечание: при class template argument deduction , шаблонный параметр шаблона класса никогда не является forwarding reference (since 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 не удаётся, дополнительно рассматриваются следующие альтернативы:

1) Если 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
2) Преобразованный 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*)
3) Если P является классом и P имеет форму simple-template-id , то преобразованный A может быть производным классом от выведенного A . Аналогично, если P является указателем на класс формы simple-template-id , преобразованный 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 , не участвуют в выводе аргументов шаблона, а вместо этого используют аргументы шаблона, которые были либо выведены в других местах, либо явно указаны. Если параметр шаблона используется только в невыводимых контекстах и не указан явно, вывод аргументов шаблона завершается неудачей.

1) Спецификатор вложенного имени (nested-name-specifier) (всё, что находится слева от оператора разрешения области видимости :: ) типа, который был указан с использованием квалифицированного идентификатора :
// 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) Постоянный аргумент шаблона или граница массива, в которых подвыражение ссылается на параметр шаблона:
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
5) Параметр шаблона, используемый в типе параметра функции, который имеет аргумент по умолчанию, применяемый при вызове, для которого выполняется вывод аргументов:
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)
6) Параметр 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
7) Параметр 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 находится в невыводимом контексте
                   // ошибка: T не указан явно и не выведен из другого P/A
g2({1, 2, 3}, 10); // P1 = std::vector<T>, A1 = {1, 2, 3}: T находится в невыводимом контексте
                   // P2 = T, A2 = int: выведено T = int
8) Параметр 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
9) Список шаблонных параметров, который появляется внутри параметра 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)
10) Для 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 , который зависит от одного или нескольких параметров шаблона типа T i, параметров шаблона-шаблона TT i, или константных параметров шаблона I i, и соответствующего аргумента A , вывод происходит, если P имеет одну из следующих форм:

  • cv (опционально) T ;
  • T* ;
  • T& ;
  • T&& ;
(начиная с C++11)
  • T (необязательно) [ I (необязательно) ] ;
  • T (опционально) ( U (опционально) ) ;
(до C++17)
  • T (опционально) ( U (опционально) ) noexcept( I (опционально) ) ;
(начиная с C++17)
  • T (необязательно) U (необязательно) ::* ;
  • TT (необязательно) <T> ;
  • TT (необязательно) <I> ;
  • TT (необязательно) <TU> ;
  • TT (необязательно) <> .

В приведённых выше формах,

  • T (опционально) или U (опционально) представляет тип или parameter-type-list , который либо удовлетворяет этим правилам рекурсивно, является невыводимым контекстом в P или A , либо является тем же самым независимым типом в P и A .
  • TT (опционально) или TU (опционально) представляет либо шаблон класса, либо шаблонный параметр шаблона.
  • I (опционально) представляет выражение, которое либо является I , является зависимым по значению в P или A , либо имеет то же самое константное значение в P и A .
  • noexcept( I (необязательно) ) представляет спецификацию исключений , в которой операнд возможно-неявного noexcept спецификатора удовлетворяет правилам для I (необязательно) выше.
(начиная с C++17)

Если P имеет одну из форм, включающих список параметров шаблона <T> или <I> , то каждый элемент P i этого списка аргументов шаблона сопоставляется с соответствующим аргументом шаблона A i из его A . Если последний P i является расширением пачки, то его шаблон сравнивается с каждым оставшимся аргументом в списке аргументов шаблона A . Завершающая пачка параметров, которая не выведена иным образом, выводится как пустая пачка параметров.

Если P имеет одну из форм, включающих список параметров функции (T) , то каждый параметр P i из этого списка сравнивается с соответствующим аргументом A i из списка параметров функции A . Если последний P i является расширением пачки (pack expansion), то его декларатор сравнивается с каждым оставшимся A i в списке типов параметров A .

Формы могут быть вложенными и обрабатываться рекурсивно:

  • X < int > ( * ) ( char [ 6 ] ) является примером T* , где T это X < int > ( char [ 6 ] ) ;
  • X < int > ( char [ 6 ] ) является примером T (опционально) ( U (опционально) ) , где T это X < int > , а U это char [ 6 ] ;
(до C++17)
  • X < int > ( char [ 6 ] ) является примером T (опционально) ( U (опционально) ) noexcept( I (опционально) ) , где T это X < int > , U это char [ 6 ] , а I в неявной спецификации noexcept это false ;
(начиная с 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>

Тип N в типе T[N] является std::size_t .

template<class T, T i>
void f(int (&a)[i]);
int v[10];
f(v); // OK: T is std::size_t

Тип B в спецификаторе noexcept ( B ) типа функции является bool .

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)

Если постоянный шаблонный параметр шаблона функции используется в списке шаблонных параметров параметра функции (который также является шаблоном), и соответствующий аргумент шаблона выводится, тип выведенного аргумента шаблона (как указано в его enclosing списке шаблонных параметров, то есть ссылки сохраняются) должен точно соответствовать типу постоянного шаблонного параметра, за исключением того, что cv-квалификаторы отбрасываются, и за исключением случаев, когда аргумент шаблона выводится из границы массива — в этом случае допускается любой целочисленный тип, даже bool хотя он всегда становится true :

template<int i>
class A {};
template<short s>
void f(A<s>); // тип параметра шаблона-константы - short
void k1()
{
    A<1> a;  // тип параметра шаблона-константы для a - int
    f(a);    // P = A<(short)s>, A = A<(int)1>
             // ошибка: выведенный параметр шаблона-константы имеет не тот же тип,
             // что и соответствующий аргумент шаблона
    f<1>(a); // OK: аргумент шаблона не выводится,
             // этот вызов вызывает 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>
           // тип параметра - int&
           // тип аргумента - int& в объявлении шаблона структуры X
           // OK (с CWG 2091): выводит R как ссылку на n
}

Параметр шаблона типа не может быть выведен из типа аргумента по умолчанию функции:

template<typename T>
void f(T = 5, T = 7);
void g()
{
    f(1);     // OK: вызывает f<int>(1, 7)
    f();      // ошибка: невозможно вывести T
    f<int>(); // OK: вызывает f<int>(5, 7)
}

Вывод шаблонного параметра-шаблона может использовать тип, применяемый в специализации шаблона, используемой в вызове функции:

template<template<typename> class X>
struct A {}; // A - шаблон с параметром TT
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>: выведено TT = B, вызов f(A<B>)

Другие контексты

Помимо вызовов функций и выражений с операторами, вывод аргументов шаблона используется в следующих ситуациях:

Вывод типа auto

Вывод аргументов шаблона используется в объявлениях переменных при определении значения спецификатора auto из инициализатора переменной.

Параметр P получается следующим образом: в T , объявленном типе переменной, который включает auto , каждое вхождение auto заменяется на воображаемый параметр шаблона типа U или, если инициализация является copy-list-инициализацией, на std::initializer_list<U> . Аргумент A - это выражение инициализатора. После вывода U из P и A по правилам, описанным выше, выведенный U подставляется в P для получения фактического типа переменной:

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 в возвращаемом типе функции из оператора return.

Для функций с автоматическим определением возвращаемого типа параметр P получается следующим образом: в T (объявленном возвращаемом типе функции, содержащем auto ), каждое вхождение auto заменяется воображаемым параметром шаблона типа U . Аргумент A представляет собой выражение в операторе return , и если оператор return не имеет операнда, A является void ( ) . После вывода U из P и A по правилам, описанным выше, выведенный U подставляется в T для получения фактического возвращаемого типа:

auto f() { return 42; } // P = auto, A = 42:
                        // deduced U = int, the return type of f is int

Если такая функция имеет несколько операторов return, вывод выполняется для каждого оператора return. Все полученные типы должны быть одинаковыми и становятся фактическим возвращаемым типом.

Если такая функция не имеет оператора return, A является void ( ) при выводе.

Примечание: значение заполнителя decltype ( auto ) в объявлениях переменных и функций не использует вывод аргументов шаблона.

(начиная с C++14)

Разрешение перегрузки

Вывод аргументов шаблона используется во время разрешения перегрузки , при генерации специализаций из функции-кандидата шаблона. P и A такие же, как в обычном вызове функции:

std::string s;
std::getline(std::cin, s);
// "std::getline" именует 4 шаблона функций,
// 2 из которых являются кандидатами (правильное количество параметров)
// 1-й кандидат-шаблон:
// P1 = std::basic_istream<CharT, Traits>&, A1 = std::cin
// P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s
// дедукция определяет параметры шаблона типов CharT, Traits и Allocator
// специализация std::getline<char, std::char_traits<char>, std::allocator<char>>
// 2-й кандидат-шаблон:
// P1 = std::basic_istream<CharT, Traits>&&, A1 = std::cin
// P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s
// дедукция определяет параметры шаблона типов CharT, Traits и Allocator
// специализация std::getline<char, std::char_traits<char>, std::allocator<char>>
// разрешение перегрузки ранжирует привязку ссылки от lvalue std::cin
// и выбирает первую из двух кандидатных специализаций

Если вывод завершается неудачей или если вывод завершается успешно, но создаваемая специализация окажется некорректной (например, перегруженный оператор, параметры которого не являются типами классов или перечислений), специализация не включается в набор перегрузок, аналогично SFINAE .

Адрес набора перегруженных функций

Вывод аргументов шаблона используется при взятии адреса перегруженного набора , который включает шаблоны функций.

Тип функции шаблона функции — P . Целевой тип — это тип A :

std::cout << std::endl;
// std::endl обозначает шаблон функции
// тип endl P =
// std::basic_ostream<CharT, Traits>& (std::basic_ostream<CharT, Traits>&)
// параметр operator<< A =
// std::basic_ostream<char, std::char_traits<char>>& (*)(
//   std::basic_ostream<char, std::char_traits<char>>&
// )
// (другие перегрузки operator<< не подходят)
// выведение определяет параметры шаблона типа CharT и Traits

В этом случае применяется дополнительное правило для вывода: при сравнении параметров функции P i и A i, если любой P i является rvalue-ссылкой на cv-неквалифицированный параметр шаблона ("передающая ссылка") и соответствующий A i является lvalue-ссылкой, тогда P i корректируется до типа параметра шаблона (T&& становится T).

Если возвращаемый тип шаблона функции является плейсхолдером ( auto или decltype ( auto ) ), этот возвращаемый тип является невыводимым контекстом и определяется при инстанциации.

(since C++14)

Частичное упорядочивание

Вывод аргументов шаблона используется при частичном упорядочении перегруженных шаблонов функций .

Шаблон функции преобразования

Вывод аргументов шаблона используется при выборе пользовательской функции преобразования аргументов шаблона.

A — это тип, требуемый в качестве результата преобразования. P — это возвращаемый тип шаблона функции преобразования. Если P является ссылочным типом, то вместо P для последующих частей раздела используется указываемый тип.

Если A не является ссылочным типом:

а) если P является типом массива, то тип указателя, полученный преобразованием массива в указатель, используется вместо P ;
б) если P является типом функции, то тип указателя на функцию, полученный в результате преобразования функции в указатель, используется вместо P ;
c) если P является cv-квалифицированным, то cv-квалификаторы верхнего уровня игнорируются.

Если A является cv-квалифицированным, cv-квалификаторы верхнего уровня игнорируются. Если A является ссылочным типом, для вывода используется тип, на который указывает ссылка.

Если обычное выведение из P и A (как описано выше) не удаётся, дополнительно рассматриваются следующие альтернативы:

а) если A является ссылочным типом, A может быть более cv-квалифицирован, чем выведенный A ;
b) если 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*
// обычное выведение для вызова функции
// template<class T> void f(T*** p) как если бы вызывалось с аргументом
// типа const int* const* const* завершается неудачей
// дополнительное выведение для функций преобразования определяет T = int
// (выведенный A - это int***, преобразуемый в const int* const* const*)
c) если A является типом указателя на функцию, выведенный A может быть указателем на noexcept функцию, преобразуемой в A через преобразование указателя на функцию;
d) если A является указателем на функцию-член, выведенный A может быть указателем на noexcept функцию-член, преобразуемой в A через преобразование указателя на функцию.
(начиная с C++17)

См. member template для других правил, касающихся шаблонов функций преобразования.

Явное инстанцирование

Вывод аргументов шаблона используется в явных инстанциациях , явных специализациях и тех объявлениях дружественных функций , где идентификатор-декларатор ссылается на специализацию шаблона функции (например, friend ostream & operator << <> ( ... ) ), если не все аргументы шаблона явно указаны или заданы по умолчанию, для определения на какую специализацию шаблона происходит ссылка используется вывод аргументов шаблона.

P — это тип шаблона функции, который рассматривается как потенциальное соответствие, а A — тип функции из объявления. Если соответствий нет или имеется более одного соответствия (после частичного упорядочивания), объявление функции является некорректным:

template<class X>
void f(X a);        // 1-й шаблон f
template<class X>
void f(X* a);       // 2-й шаблон f
template<>
void f<>(int* a) {} // явная специализация f
// P1 = void(X), A1 = void(int*): выведено X = int*, f<int*>(int*)
// P2 = void(X*), A2 = void(int*): выведено X = int, f<int>(int*)
// f<int*>(int*) и f<int>(int*) затем передаются в частичное упорядочивание
// которое выбирает f<int>(int*) как более специализированный шаблон

В этом случае применяется дополнительное правило для вывода: при сравнении параметров функции P i и A i, если любой P i является rvalue-ссылкой на cv-неквалифицированный параметр шаблона ("forwarding reference") и соответствующий A i является lvalue-ссылкой, тогда P i корректируется к типу параметра шаблона (T&& становится T).

Шаблон функции деаллокации

Вывод аргументов шаблона используется при определении, соответствует ли специализация шаблона функции освобождения данной placement-форме 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; // когда X() выбрасывает исключение, выполняется поиск operator delete
                              // P1 = void(void*, T), A1 = void(void*, bool):
                              // выведенный T = bool
                              // P2 = void(void*, T), A2 = void(void*, double):
                              // выведенный T = double
                              // разрешение перегрузки выбирает operator delete<bool>
    }
    catch(const std::exception&) {}
    try
    {
        X* p1 = new (13.2) X; // аналогичный поиск, выбирает 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: выведено TT = vector
template<template<class> class TT>
void f(TT<int>);
f(v); // ошибка: TT не может быть выведен как "Vec", потому что Vec является псевдонимом шаблона

Неявные преобразования

Вывод типов не учитывает неявные преобразования (кроме корректировок типов, перечисленных выше): это задача разрешения перегрузки , которая происходит позже. Однако, если вывод успешен для всех параметров, участвующих в выводе аргументов шаблона, и все неуказанные аргументы шаблона явно заданы или имеют значения по умолчанию, то оставшиеся параметры функции сравниваются с соответствующими аргументами функции. Для каждого оставшегося параметра 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); // для #1: вывод определяет T = struct A, но оставшийся аргумент 1
             // не может быть неявно преобразован в параметр void*: вывод завершается неудачей
             // инстанцирование возвращаемого типа не запрашивается
             // для #2: вывод определяет T = struct A, и оставшийся аргумент 1
             // может быть неявно преобразован в параметр int: вывод завершается успешно
             // вызов функции компилируется как вызов #2 (неудача вывода - это 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 невозможно вывести границу массива и тип элемента из braced-init-list вывод разрешен
CWG 2052 C++98 вывод оператора с не-классовыми
не-перечисляемыми аргументами был жесткой ошибкой
мягкая ошибка если
есть другие перегрузки
CWG 2091 C++98 вывод ссылочного константного параметра не
работал из-за несоответствия типов с аргументом
несоответствие типов устранено
N3922 C++11 прямая инициализация списком для auto выводит std::initializer_list некорректна для более чем одного
элемента, выводит тип элемента
для одного элемента
CWG 2355 C++17 значение в noexcept спецификаторе типа функции не было выводимым сделано выводимым