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 является ссылкой-значением на cv-неквалифицированный параметр шаблона (так называемая ссылка-перенаправления), и соответствующий аргумент вызова функции является l-значением, тип l-ссылки на A используется вместо 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 имеет вид простого-идентификатора-шаблона, то преобразованный A может быть производным классом от выведенного A. Аналогично, если P — указатель на класс вида простой-идентификатор-шаблона, то преобразованный 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) спецификатор-имени-вложенного-имени (все слева от оператора разрешения области ::) типа, который был указан с помощью квалифицированного-идентификатора:
// 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)-объект, или ссылка на массив(с C++17):
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
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, который зависит от одного или нескольких параметров шаблона типа 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 в типе возвращаемого значения функции из оператора возврата.

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

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

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

(с C++14)

Частичный порядок

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

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

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

A — это тип, который требуется в результате преобразования. P — тип возвращаемого значения шаблона функции преобразования. Если 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*)
в) если A — тип указателя на функцию, выведенный A может быть указателем на функцию noexcept, преобразуемый в A с помощью преобразования указателя на функцию; г) если A — тип указателя на член функции, выведенный A может быть указателем на noexcept-функцию члена, преобразуемый в A с помощью преобразования указателя на функцию. (с C++17)

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

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

Вывод шаблона аргумента используется в явных инстанцированиях, явных специализациях и тех объявлениях friend, где идентификатор-декларатор ссылается на специализацию шаблона функции (например, 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/w/cpp/language/template_argument_deduction

Spec-Zone.ru

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