Template argument deduction
Для создания экземпляра шаблона функции должны быть известны все аргументы шаблона, но не все аргументы шаблона обязательно указывать явно. Когда это возможно, компилятор выводит недостающие аргументы шаблона из аргументов функции. Это происходит при попытке вызова функции, при взятии адреса шаблона функции и в некоторых других контекстах :
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 }
Этот механизм позволяет использовать шаблонные операторы, поскольку нет синтаксиса для указания шаблонных аргументов для оператора, кроме как переписав его в виде выражения вызова функции:
Вывод аргументов шаблона происходит после поиска имени шаблона функции (который может включать поиск, зависимый от аргументов ) и до подстановки аргументов шаблона (которая может включать 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) |
Содержание
Вывод из вызова функцииПопытка вывода аргументов шаблона определяет аргументы шаблона (типы для параметров-типов Если присутствует несколько параметров, каждая пара
Если 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
Перед началом вывода к 1) Если
P не является ссылочным типом,а) если
A является типом массива, A заменяется типом указателя, полученным в результате преобразования массива в указатель;б) иначе, если
A является типом функции, A заменяется типом указателя, полученным в результате преобразования функции в указатель;в) иначе, если
A является cv-квалифицированным типом, верхние cv-квалификаторы игнорируются при выводе:
template<class T>
void f(T);
int a[3];
f(a); // P = T, A = int[3], adjusted to int*: deduced T = int*
void b(int);
f(b); // P = T, A = void(int), adjusted to void(*)(int): deduced T = void(*)(int)
const int c = 13;
f(c); // P = T, A = const int, adjusted to int: deduced T = int
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
}
После этих преобразований вывод обрабатывается, как описано ниже (см. раздел вывод из типа) и пытается найти такие аргументы шаблона, которые сделали бы выведенный Если обычный вывод из 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 (имя шаблона со списком аргументов шаблона) или спецификатор сращивания со списком аргументов шаблона(начиная с C++26), то преобразованный A может быть производным классом от выведенного A. Аналогично, если P является указателем на класс формы simple-template-id (имя шаблона со списком аргументов шаблона) или спецификатор сращивания со списком аргументов шаблона(начиная с 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
}
Контексты без вывода
В следующих случаях типы, шаблоны и константы, используемые для формирования
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>
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
или набор перегрузок включает одну или несколько шаблонов функций:
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
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
>
,
Вывод из типа
Для параметра функции
В приведённых выше формах,
Если
Если
Формы могут быть вложенными и обрабатываться рекурсивно:
Если постоянный шаблонный параметр шаблона функции используется в списке шаблонных параметров параметра функции (который также является шаблоном), и соответствующий аргумент шаблона выводится, тип выведенного аргумента шаблона (как указано в его 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>) Другие контекстыПомимо вызовов функций и выражений с операторами, вывод аргументов шаблона используется в следующих ситуациях:
Разрешение перегрузки
Вывод аргументов шаблона используется во время
разрешения перегрузки
, при генерации специализаций из функции-кандидата шаблона.
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 . Адрес набора перегруженных функцийВывод аргументов шаблона используется при взятии адреса перегруженного набора , который включает шаблоны функций.
Тип функции шаблона функции —
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
является типом массива, то тип указателя, полученный преобразованием массива в указатель, используется вместо
P
;
б)
если
P
является типом функции, то тип указателя на функцию, полученный в результате преобразования функции в указатель, используется вместо
P
;
c)
если
P
является cv-квалифицированным, то cv-квалификаторы верхнего уровня игнорируются.
Если
Если обычное выведение из
а)
если
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*)
См. member template для других правил, касающихся шаблонов функций преобразования. Явное инстанцированиеВывод аргументов шаблона используется в явных инстанциациях , явных специализациях и тех объявлениях дружественных функций , где идентификатор-декларатор ссылается на специализацию шаблона функции (например, friend ostream & operator << <> ( ... ) ), если не все аргументы шаблона явно указаны или заданы по умолчанию, для определения на какую специализацию шаблона происходит ссылка используется вывод аргументов шаблона.
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*) как более специализированный шаблон
В этом случае применяется дополнительное правило для вывода: при сравнении параметров функции
Шаблон функции деаллокации
Вывод аргументов шаблона используется при определении, соответствует ли специализация шаблона
функции освобождения
данной placement-форме
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 является псевдонимом шаблона Неявные преобразования
Вывод типов не учитывает неявные преобразования (кроме корректировок типов, перечисленных выше): это задача
разрешения перегрузки
, которая происходит позже. Однако, если вывод успешен для всех параметров, участвующих в выводе аргументов шаблона, и все неуказанные аргументы шаблона явно заданы или имеют значения по умолчанию, то оставшиеся параметры функции сравниваются с соответствующими аргументами функции. Для каждого оставшегося параметра
Параметры с зависимыми типами, в которых ни один параметр-шаблона не участвует в выводе аргументов шаблона, и параметры, ставшие независимыми вследствие подстановки явно указанных аргументов шаблона, будут проверяться во время разрешения перегрузки: 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++.
|