Spec-Zone.ru › C++

Выведение аргументов шаблона

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

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-квалификаторов из P получается std::initializer_list<P'>, а A является списком инициализации в фигурных скобках, выведение выполняется для каждого элемента списка инициализации: в качестве параметра используется 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 является непустым списком инициализации в фигурных скобках, выведение выполняется аналогичным образом, за исключением случая, когда 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

Если пакет параметров появляется в последней позиции 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 = 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 применяются следующие преобразования:

1) Если P не является ссылочным типом,
a) если A является типом массива, A заменяется типом указателя, полученным в результате преобразования массива в указатель;
b) иначе, если 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-ссылкой на 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 завершается неудачей, дополнительно рассматриваются следующие варианты:

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 имеет форму простого идентификатора шаблона (имени шаблона со списком аргументов шаблона) или спецификатора 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, не участвуют в выведении аргументов шаблона, а вместо этого используют аргументы шаблона, выведенные в других местах или указанные явно. Если параметр шаблона используется только в невыводимых контекстах и не указан явно, выведение аргументов шаблона завершается неудачей.

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) Выражение в спецификаторе 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)
5) Нетиповый аргумент шаблона или граница массива, в которых подвыражение ссылается на параметр шаблона:
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
6) Параметр шаблона, используемый в типе параметра функции, имеющего аргумент по умолчанию, который используется в вызове, для которого выполняется выведение аргументов:
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)
7) Параметр 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
8) Параметр 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)
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&;
  • 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(необязательно) обозначает тип или список типов параметров, который либо рекурсивно удовлетворяет этим правилам, либо является невыводимым контекстом в P или A, либо представляет собой один и тот же независящий тип в P и A.
  • TT(необязательно) или TU(необязательно) обозначает шаблон класса или параметр шаблона-шаблона.
  • I(необязательно) обозначает выражение, которое либо является I, либо зависит от значения в P или A, либо имеет одинаковое постоянное значение в P и A.
  • noexcept(I(необязательно)) обозначает спецификацию исключений, в которой операнд, возможно неявного, спецификатора noexcept удовлетворяет приведённым выше правилам для I(необязательно).
(начиная с 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]);
  • 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)

Если нетиповый параметр шаблона функции используется в списке параметров шаблона параметра функции (который также является шаблоном), а соответствующий аргумент шаблона выведен, тип выведенного аргумента шаблона (как задано в окружающем списке параметров шаблона, то есть ссылки сохраняются) должен точно соответствовать типу нетипового параметра шаблона, за исключением того, что 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 по инициализатору переменной.

Параметр P получается следующим образом: в T объявленный тип переменной, содержащий auto, преобразуется заменой каждого вхождения auto на воображаемый параметр шаблона типа U или, если инициализация выполняется копированием со списком, на 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

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

Для функций с возвращаемым типом auto параметр 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, при выведении A равен void().

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

(начиная с 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 или decltype(auto)), этот тип возвращаемого значения является невыводимым контекстом и определяется при инстанцировании.

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

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

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

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

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

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

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

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

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

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

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*
// 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API