Class template argument deduction (CTAD) (since C++17)
Для создания экземпляра шаблона класса должны быть известны все аргументы шаблона, но не все аргументы шаблона должны быть указаны явно. В следующих контекстах компилятор выведет аргументы шаблона из типа инициализатора:
- любое объявление , которое задает инициализацию переменной и variable template, чей объявленный тип является шаблоном класса (возможно cv-qualified ):
std::pair p(2, 4.5); // выводится как std::pair<int, double> p(2, 4.5); std::tuple t(4, 3, 2.5); // аналогично auto t = std::make_tuple(4, 3, 2.5); std::less l; // аналогично std::less<void> l;
template<class T> struct A { A(T, T); }; auto y = new A{1, 2}; // выделенный тип - A<int>
- функциональное приведение типа выражения:
auto lck = std::lock_guard(mtx); // выводится как std::lock_guard<std::mutex> std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); // выводится как std::back_insert_iterator<T>, // где T - тип контейнера vi2 std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); // выводится как Foo<T>, // где T - уникальный тип лямбда-выражения
template<class T> struct X { constexpr X(T) {} }; template<X x> struct Y {}; Y<0> y; // OK, Y<X<int>(0)> |
(since C++20) |
Содержание |
Дедукция для шаблонов классов
Неявно генерируемые направляющие вывода
Когда в функциональном приведении типа или в объявлении переменной спецификатор типа состоит исключительно
из имени основного шаблона класса
C
(т.е. отсутствует сопровождающий список аргументов шаблона), кандидаты для вывода формируются следующим образом:
-
Если
Cопределен, для каждого конструктора (или шаблона конструктора)C iобъявленного в указанном основном шаблоне, создается фиктивная шаблонная функцияF i, удовлетворяющая всем следующим условиям:
-
-
Шаблонные параметры
F i— это шаблонные параметрыCс последующим (еслиC iявляется шаблоном конструктора) добавлением шаблонных параметровC i(аргументы по умолчанию для шаблонов также включаются).
-
Шаблонные параметры
|
(начиная с C++20) |
-
-
Список параметров
функции
F iявляется списком параметров классаC i. -
Возвращаемый тип функции
F iпредставляет собойCс последующими шаблонными параметрами шаблона класса, заключенными в<>.
-
Список параметров
функции
-
Если
Cне определен или не объявляет никаких конструкторов, добавляется дополнительная фиктивная шаблонная функция, производная, как указано выше, от гипотетического конструктораC().
-
В любом случае, добавляется дополнительная фиктивная шаблонная функция, выведенная, как описано выше, из гипотетического конструктора
C(C), называемая кандидатом для вывода копирования.
-
Для каждого
пользовательского руководства по выводу
G i, создаётся фиктивная функция или шаблон функцииF i, удовлетворяющая всем следующим условиям:
-
-
Список параметров
F iявляется списком параметровG i. -
Возвращаемый тип
F iявляется простым шаблонным идентификаторомG i. -
Если
G iимеет параметры шаблона (синтаксис (2) ),F iявляется шаблоном функции, и его список параметров шаблона является списком параметров шаблонаG i. В противном случае,F iявляется функцией.
-
Список параметров
template<class T> struct A { T t; struct { long a, b; } u; }; A a{1, 2, 3}; // aggregate deduction candidate: // template<class T> // A<T> F(T, long, long); template<class... Args> struct B : std::tuple<Args...>, Args... {}; B b{std::tuple<std::any, std::string>{}, std::any{}}; // aggregate deduction candidate: // template<class... Args> // B<Args...> F(std::tuple<Args...>, Args...); // type of b is deduced as B<std::any, std::string> |
(начиная с C++20) |
Вывод аргументов шаблона
и
разрешение перегрузки
затем выполняются для инициализации фиктивного объекта гипотетического типа класса, сигнатуры конструкторов которого соответствуют руководствам (за исключением типа возвращаемого значения) с целью формирования набора перегрузки, а инициализатор предоставляется контекстом, в котором выполнялся вывод аргументов шаблона класса, за исключением того, что первая фаза
списковой инициализации
(рассмотрение конструкторов со списком инициализации) опускается, если список инициализации состоит из одного выражения типа (возможно, cv-квалифицированного)
U
, где
U
является специализацией
C
или классом, производным от специализации
C
.
Эти вымышленные конструкторы являются открытыми членами гипотетического типа класса. Они являются explicit, если руководство было сформировано из explicit конструктора. Если разрешение перегрузки завершается неудачей, программа является некорректной. В противном случае возвращаемый тип выбранной специализации шаблона
F
становится выведенной специализацией шаблона класса.
template<class T> struct UniquePtr { UniquePtr(T* t); }; UniquePtr dp{new auto(2.0)}; // Один объявленный конструктор: // C1: UniquePtr(T*); // Набор неявно сгенерированных направляющих вывода: // F1: template<class T> // UniquePtr<T> F(T* p); // F2: template<class T> // UniquePtr<T> F(UniquePtr<T>); // кандидат копирующего вывода // воображаемый класс для инициализации: // struct X // { // template<class T> // X(T* p); // из F1 // // template<class T> // X(UniquePtr<T>); // из F2 // }; // прямая инициализация объекта X // с "new double(2.0)" в качестве инициализатора // выбирает конструктор, соответствующий направляющей F1 с T = double // Для F1 с T=double, возвращаемый тип - UniquePtr<double> // результат: // UniquePtr<double> dp{new auto(2.0)}
Или, для более сложного примера (примечание: "
S::N
" не скомпилируется: квалификаторы разрешения области видимости не могут быть выведены):
template<class T> struct S { template<class U> struct N { N(T); N(T, U); template<class V> N(V, U); }; }; S<int>::N x{2.0, 1}; // неявно сгенерированные направляющие вывода (заметим, что T уже известно как int) // F1: template<class U> // S<int>::N<U> F(int); // F2: template<class U> // S<int>::N<U> F(int, U); // F3: template<class U, class V> // S<int>::N<U> F(V, U); // F4: template<class U> // S<int>::N<U> F(S<int>::N<U>); (кандидат копирующего вывода) // Разрешение перегрузки для direct-list-init с "{2.0, 1}" в качестве инициализатора // выбирает F3 с U=int и V=double. // Возвращаемый тип - S<int>::N<int> // результат: // S<int>::N<int> x{2.0, 1};
Пользовательские правила вывода
Синтаксис пользовательского руководства по выводу представляет собой синтаксис объявления функции (шаблона) с завершающим возвращаемым типом, за исключением того, что в качестве имени функции используется имя шаблона класса:
explicit
(необязательно)
template-name
(
parameter-list
)
->
simple-template-id
requires-clause
(необязательно)
;
|
(1) | ||||||||
template <
template-parameter-list
>
requires-clause
(необязательно)
explicit (необязательно) template-name
(
parameter-list
)
->
simple-template-id
requires-clause
(необязательно)
;
|
(2) | ||||||||
| template-parameter-list | - | непустой разделённый запятыми список параметров шаблона |
| explicit | - |
спецификатор
explicit
|
| template-name | - | имя шаблона класса, аргументы которого должны быть выведены |
| parameter-list | - | (возможно, пустой) список параметров |
| simple-template-id | - | простой идентификатор шаблона |
| requires-clause | - | (начиная с C++20) условие requires |
|
Параметры пользовательских направляющих вывода не могут иметь типы-заполнители: сокращённый синтаксис шаблонов функций не допускается. |
(since C++20) |
Определяемые пользователем правила вывода должны называть шаблон класса и должны быть введены в той же семантической области видимости шаблона класса (которой может быть пространство имен или объемлющий класс) и, для шаблона класса-члена, должны иметь тот же уровень доступа, но правила вывода не становятся членами этой области видимости.
Руководство по выводу не является функцией и не имеет тела. Руководства по выводу не обнаруживаются при поиске по имени и не участвуют в разрешении перегрузки, за исключением разрешения перегрузки против других руководств по выводу при выводе аргументов шаблона класса. Руководства по выводу не могут быть переобъявлены в той же единице трансляции для того же шаблона класса.
// объявление шаблона template<class T> struct container { container(T t) {} template<class Iter> container(Iter beg, Iter end); }; // дополнительное руководство по выводу template<class Iter> container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>; // использование container c(7); // OK: выводит T=int с использованием неявно сгенерированного руководства std::vector<double> v = {/* ... */}; auto d = container(v.begin(), v.end()); // OK: выводит T=double container e{5, 6}; // Ошибка: не существует std::iterator_traits<int>::value_type
Фиктивные конструкторы для целей разрешения перегрузки (описанные выше) являются явными, если они соответствуют неявно сгенерированному направляющему выводу, образованному из явного конструктора, или пользовательскому направляющему выводу, который объявлен explicit . Как всегда, такие конструкторы игнорируются в контексте копирующей инициализации:
template<class T> struct A { explicit A(const T&, ...) noexcept; // #1 A(T&&, ...); // #2 }; int i; A a1 = {i, i}; // ошибка: невозможно вывести из rvalue-ссылки в #2, // и #1 является explicit и не рассматривается при copy-инициализации. A a2{i, i}; // OK, #1 выводит A<int> и также инициализирует A a3{0, i}; // OK, #2 выводит A<int> и также инициализирует A a4 = {0, i}; // OK, #2 выводит A<int> и также инициализирует template<class T> A(const T&, const T&) -> A<T&>; // #3 template<class T> explicit A(T&&, T&&) -> A<T>; // #4 A a5 = {0, 1}; // ошибка: #3 выводит A<int&> // и #1 & #2 приводят к одинаковым конструкторам параметров. A a6{0, 1}; // OK, #4 выводит A<int> и #2 инициализирует A a7 = {0, i}; // ошибка: #3 выводит A<int&> A a8{0, i}; // ошибка: #3 выводит A<int&> // Примечание: проверьте https://github.com/cplusplus/CWG/issues/647, утверждающий, что // примеры a7 и a8 некорректны и возможно должны быть заменены на //A a7 = {0, i}; // ошибка: #2 и #3 оба подходят, разрешение перегрузки завершается неудачей //A a8{i,i}; // ошибка: #3 выводит A<int&>, // // #1 и #2 объявляют одинаковый конструктор
Использование члена-typedef или псевдонима шаблона в списке параметров конструктора или шаблона конструктора само по себе не делает соответствующий параметр невыводимым контекстом в неявно сгенерированном руководстве.
template<class T> struct B { template<class U> using TA = T; template<class U> B(U, TA<U>); // #1 }; // Неявно сгенерированное правило вывода из #1 эквивалентно // template<class T, class U> // B(U, T) -> B<T>; // а не // template<class T, class U> // B(U, typename B<T>::template TA<U>) -> B<T>; // что не поддавалось бы выводу B b{(int*)0, (char*)0}; // OK, выводит B<char*>
Вывод для псевдонимов шаблонов
Когда приведение в стиле функции или объявление переменной использует имя псевдонима шаблона
template<class T> class unique_ptr { /* ... */ }; template<class T> class unique_ptr<T[]> { /* ... */ }; template<class T> unique_ptr(T*) -> unique_ptr<T>; // #1 template<class T> unique_ptr(T*) -> unique_ptr<T[]>; // #2 template<class T> concept NonArray = !std::is_array_v<T>; template<NonArray A> using unique_ptr_nonarray = unique_ptr<A>; template<class A> using unique_ptr_array = unique_ptr<A[]>; // сгенерированная направляющая для unique_ptr_nonarray: // из #1 (вывод unique_ptr<T> из unique_ptr<A> дает T = A): // template<class A> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<A>>) // auto F(A*) -> unique_ptr<A>; // из #2 (вывод unique_ptr<T[]> из unique_ptr<A> ничего не дает): // template<class T> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<T[]>>) // auto F(T*) -> unique_ptr<T[]>; // где argument_of_unique_ptr_nonarray_is_deducible_from может быть определен как // template<class> // class AA; // template<NonArray A> // class AA<unique_ptr_nonarray<A>> {}; // template<class T> // concept argument_of_unique_ptr_nonarray_is_deducible_from = // requires { sizeof(AA<T>); }; // сгенерированная направляющая для unique_ptr_array: // из #1 (вывод unique_ptr<T> из unique_ptr<A[]> дает T = A[]): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A(*)[]) -> unique_ptr<A[]>; // из #2 (вывод unique_ptr<T[]> из unique_ptr<A[]> дает T = A): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A*) -> unique_ptr<A[]>; // где argument_of_unique_ptr_array_is_deducible_from может быть определен как // template<class> // class BB; // template<class A> // class BB<unique_ptr_array<A>> {}; // template<class T> // concept argument_of_unique_ptr_array_is_deducible_from = // requires { sizeof(BB<T>); }; // Использование: unique_ptr_nonarray p(new int); // выведено в unique_ptr<int> // направляющая вывода, сгенерированная из #1, возвращает unique_ptr<int> // направляющая вывода, сгенерированная из #2, возвращает unique_ptr<int[]>, которая игнорируется, потому что // argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<int[]>> не выполняется unique_ptr_array q(new int[42]); // выведено в unique_ptr<int[]> // направляющая вывода, сгенерированная из #1, завершается неудачей (не может вывести A в A(*)[] из new int[42]) // направляющая вывода, сгенерированная из #2, возвращает unique_ptr<int[]> |
(начиная с C++20) |
Примечания
Вывод аргументов шаблона класса выполняется только при отсутствии списка аргументов шаблона. Если список аргументов шаблона указан, вывод не производится.
std::tuple t1(1, 2, 3); // OK: выведение типов std::tuple<int, int, int> t2(1, 2, 3); // OK: все аргументы предоставлены std::tuple<> t3(1, 2, 3); // Ошибка: нет подходящего конструктора в tuple<>. // Выведение типов не выполняется. std::tuple<int> t4(1, 2, 3); // Ошибка
|
Вывод аргументов шаблона класса для агрегатов обычно требует пользовательских направляющих вывода: template<class A, class B> struct Agg { A a; B b; }; // implicitly-generated guides are formed from default, copy, and move constructors template<class A, class B> Agg(A a, B b) -> Agg<A, B>; // ^ This deduction guide can be implicitly generated in C++20 Agg agg{1, 2.0}; // deduced to Agg<int, double> from the user-defined guide template<class... T> array(T&&... t) -> array<std::common_type_t<T...>, sizeof...(T)>; auto a = array{1, 2, 5u}; // deduced to array<unsigned, 3> from the user-defined guide |
(до C++20) |
Пользовательские направляющие вывода не обязаны быть шаблонами:
template<class T> struct S { S(T); }; S(char const*) -> S<std::string>; S s{"hello"}; // выводится как S<std::string>
В области видимости шаблона класса имя шаблона без списка параметров является внедренным именем класса и может использоваться как тип. В этом случае вывод аргументов шаблона не происходит, и параметры шаблона должны быть указаны явно:
template<class T> struct X { X(T) {} template<class Iter> X(Iter b, Iter e) {} template<class Iter> auto foo(Iter b, Iter e) { return X(b, e); // нет вывода: X - это текущий X<T> } template<class Iter> auto bar(Iter b, Iter e) { return X<typename Iter::value_type>(b, e); // необходимо указать, что мы хотим } auto baz() { return ::X(0); // не введенное имя класса; выводится как X<int> } };
В разрешении перегрузки частичное упорядочивание имеет приоритет над тем, сгенерирован ли шаблон функции из пользовательского руководства по выводу: если шаблон функции, сгенерированный из конструктора, более специализирован, чем сгенерированный из пользовательского руководства по выводу, выбирается вариант, сгенерированный из конструктора. Поскольку кандидат копирующего вывода обычно более специализирован, чем конструктор-обертка, это правило означает, что копирование обычно предпочтительнее обертывания.
template<class T> struct A { A(T, int*); // #1 A(A<T>&, int*); // #2 enum { value }; }; template<class T, int N = T::value> A(T&&, int*) -> A<T>; //#3 A a{1, 0}; // использует #1 для вывода A<int> и инициализирует с помощью #1 A b{a, 0}; // использует #2 (более специализирован, чем #3) для вывода A<int> и инициализирует с помощью #2
Когда предыдущие методы разрешения неоднозначностей, включая частичное упорядочивание, не позволяют различить две кандидатные шаблонные функции, применяются следующие правила:
- Шаблон функции, созданный из пользовательского руководства по выводу, предпочтительнее шаблона, неявно сгенерированного из конструктора или шаблона конструктора.
- Кандидат копирующего вывода предпочтительнее всех других шаблонов функций, неявно сгенерированных из конструктора или шаблона конструктора.
- Шаблон функции, неявно сгенерированный из нешаблонного конструктора, предпочтительнее шаблона функции, неявно сгенерированного из шаблона конструктора.
template<class T> struct A { using value_type = T; A(value_type); // #1 A(const A&); // #2 A(T, T, int); // #3 template<class U> A(int, T, U); // #4 }; // #5, кандидат копирующего вывода A(A); A x(1, 2, 3); // использует #3, сгенерированный из нешаблонного конструктора template<class T> A(T) -> A<T>; // #6, менее специализирован чем #5 A a(42); // использует #6 для вывода A<int> и #1 для инициализации A b = a; // использует #5 для вывода A<int> и #2 для инициализации template<class T> A(A<T>) -> A<A<T>>; // #7, такой же специализированный как #5 A b2 = a; // использует #7 для вывода A<A<int>> и #1 для инициализации
Ссылка на rvalue на неквалифицированный cv шаблонный параметр не является пересылающей ссылкой если этот параметр является параметром шаблона класса:
template<class T> struct A { template<class U> A(T&&, U&&, int*); // #1: T&& не является forwarding reference // U&& является forwarding reference A(T&&, int*); // #2: T&& не является forwarding reference }; template<class T> A(T&&, int*) -> A<T>; // #3: T&& является forwarding reference int i, *ip; A a{i, 0, ip}; // ошибка, невозможно вывести из #1 A a0{0, 0, ip}; // использует #1 для вывода A<int> и #1 для инициализации A a2{i, ip}; // использует #3 для вывода A<int&> и #2 для инициализации
При инициализации из единственного аргумента типа, который является специализацией рассматриваемого шаблона класса, копирующий вывод обычно предпочтительнее оборачивания по умолчанию:
std::tuple t1{1}; //std::tuple<int> std::tuple t2{t1}; //std::tuple<int>, а не std::tuple<std::tuple<int>> std::vector v1{1, 2}; // std::vector<int> std::vector v2{v1}; // std::vector<int>, а не std::vector<std::vector<int>> (P0702R1) std::vector v3{v1, v2}; // std::vector<std::vector<int>>
За исключением особого случая с копированием и обёртыванием, строгое предпочтение конструкторов со списками инициализации в list-initialization остаётся неизменным.
std::vector v1{1, 2}; // std::vector<int> std::vector v2(v1.begin(), v1.end()); // std::vector<int> std::vector v3{v1.begin(), v1.end()}; // std::vector<std::vector<int>::iterator>
До введения вывода аргументов шаблона класса распространенным подходом для избежания явного указания аргументов было использование шаблона функции:
std::tuple p1{1, 1.0}; //std::tuple<int, double>, с использованием выведения типа auto p2 = std::make_tuple(1, 1.0); //std::tuple<int, double>, до C++17
| Макрос тестирования возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_deduction_guides
|
201703L
|
(C++17) | Вывод аргументов шаблона для шаблонов классов |
201907L
|
(C++20) | CTAD для агрегатов и псевдонимов |
Отчеты о дефектах
Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Applied to | Behavior as published | Correct behavior |
|---|---|---|---|
| CWG 2376 | C++17 |
CTAD выполнялся даже если тип объявляемой переменной
отличается от шаблона класса, аргументы которого выводятся |
не выполнять
CTAD в этом случае |
| CWG 2628 | C++20 | неявные направляющие вывода не распространяли ограничения | распространять ограничения |
| CWG 2697 | C++20 |
было неясно, разрешен ли сокращенный синтаксис шаблона функции
в пользовательских направляющих вывода |
запрещено |
| CWG 2707 | C++20 | направляющие вывода не могли иметь завершающую requires клаузу | могут |
| CWG 2714 | C++17 |
неявные направляющие вывода не учитывали
аргументы по умолчанию конструкторов |
учитывать их |
| CWG 2913 | C++20 |
решение
CWG issue 2707
сделало синтаксис направляющих вывода
несогласованным с синтаксисом объявления функции |
скорректирован синтаксис |
| P0702R1 | C++17 |
конструктор initializer-list может опережать
кандидата вывода копирования, приводя к обертыванию |
фаза initializer-list
пропускается при копировании |