Вывод шаблонов аргументов класса (CTAD) (с C++17)
Для того, чтобы создать экземпляр шаблона класса, каждый аргумент шаблона должен быть известен, но не каждый аргумент шаблона должен быть указан. В следующих контекстах компилятор будет выводить аргументы шаблона из типа инициализатора:
- любое объявление, которое указывает инициализацию переменной и шаблон переменной, тип которых объявлен как шаблон класса (возможно, с квалификаторами cv):
std::pair p(2, 4.5); // deduces to std::pair<int, double> p(2, 4.5); std::tuple t(4, 3, 2.5); // same as auto t = std::make_tuple(4, 3, 2.5); std::less l; // same as std::less<void> l;
template<class T>
struct A
{
A(T, T);
};
auto y = new A{1, 2}; // allocated type is A<int>auto lck = std::lock_guard(mtx); // deduces to std::lock_guard<std::mutex>
std::copy_n(vi1, 3,
std::back_insert_iterator(vi2)); // deduces to std::back_insert_iterator<T>,
// where T is the type of the container vi2
std::for_each(vi.begin(), vi.end(),
Foo([&](int i) {...})); // deduces to Foo<T>,
// where T is the unique lambda type template<class T>
struct X
{
constexpr X(T) {}
};
template<X x>
struct Y {};
Y<0> y; // OK, Y<X<int>(0)> | (с C++20) |
Вывод для шаблонов классов
Неявные руководства по выводу
Когда в выражении приведения типов по образцу функции или в объявлении переменной спецификатор типа состоит только из имени первичного шаблона класса C (т. е. нет сопроводительного списка аргументов шаблона), кандидаты для вывода формируются следующим образом:
- Если
Cопределено, для каждого конструктора (или шаблона конструктора)Ci, объявленного в указанном первичном шаблоне, создаётся вымышленный шаблон функцииFi, такой что - параметры шаблона
Fiявляются параметрами шаблонаC(еслиCi— шаблон конструктора) и параметрами шаблонаCi(включая значения по умолчанию) - параметрами функции
Fiявляются параметры конструктора - тип возвращаемого значения
FiявляетсяCсо вложенными параметрами шаблона шаблона класса в<> - Если
Cне определено или не объявляет никаких конструкторов, добавляется дополнительный вымышленный шаблон функции, полученный как указано выше из гипотетического конструктораC() - В любом случае добавляется дополнительный вымышленный шаблон функции, полученный как указано выше из гипотетического конструктора
C(C), который называется кандидатом вывода для копирования.
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.
Эти вымышленные конструкторы являются открытыми членами гипотетического типа класса. Они явные, если руководство было сформировано из явного конструктора. Если разрешение перегрузки не удаётся, программа недействительна. В противном случае тип возвращаемого значения выбранной специализации F шаблона становится выведенной специализацией шаблона класса.
template<class T>
struct UniquePtr
{
UniquePtr(T* t);
};
UniquePtr dp{new auto(2.0)};
// One declared constructor:
// C1: UniquePtr(T*);
// Set of implicitly-generated deduction guides:
// F1: template<class T>
// UniquePtr<T> F(T* p);
// F2: template<class T>
// UniquePtr<T> F(UniquePtr<T>); // copy deduction candidate
// imaginary class to initialize:
// struct X
// {
// template<class T>
// X(T* p); // from F1
//
// template<class T>
// X(UniquePtr<T>); // from F2
// };
// direct-initialization of an X object
// with "new double(2.0)" as the initializer
// selects the constructor that corresponds to the guide F1 with T = double
// For F1 with T=double, the return type is UniquePtr<double>
// result:
// 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};
// the implicitly-generated deduction guides are (note that T is already known to be 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>); (copy deduction candidate)
// Overload resolution for direct-list-init with "{2.0, 1}" as the initializer
// chooses F3 with U=int and V=double.
// The return type is S<int>::N<int>
// result:
// S<int>::N<int> x{2.0, 1};Пользовательские руководства по выводу
Синтаксис пользовательского руководства по выводу — это синтаксис объявления функции с типом возвращаемого значения, за исключением того, что оно использует имя шаблона класса в качестве имени функции:
explicit-specifier (optional) template-name ( parameter-declaration-clause ) -> simple-template-id ; |
| Параметры пользовательских руководств по выводу не могут иметь типов заполнителей: синтаксис сокращённого шаблона функции недопустим. | (с C++20) |
Пользовательские руководства по выводу должны называть шаблон класса и должны быть введены в том же семантическом пространстве, что и шаблон класса (в пространстве имён или в окружающем классе) и, для вложенного шаблона класса, должны иметь тот же доступ, но руководства по выводу не становятся членами этого пространства имён.
Руководство по выводу не является функцией и не имеет тела. Руководства по выводу не находятся с помощью поиска по имени и не участвуют в разрешении перегрузки, за исключением разрешения перегрузки по отношению к другим руководствам по выводу при выводе аргументов шаблона класса. Руководства по выводу не могут быть повторно объявлены в одном файле перевода для одного и того же шаблона класса.
// declaration of the template
template<class T>
struct container
{
container(T t) {}
template<class Iter>
container(Iter beg, Iter end);
};
// additional deduction guide
template<class Iter>
container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;
// uses
container c(7); // OK: deduces T=int using an implicitly-generated guide
std::vector<double> v = {/* ... */};
auto d = container(v.begin(), v.end()); // OK: deduces T=double
container e{5, 6}; // Error: there is no 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}; // error: cannot deduce from rvalue reference in #2,
// and #1 is explicit, and not considered in copy-initialization.
A a2{i, i}; // OK, #1 deduces to A<int> and also initializes
A a3{0, i}; // OK, #2 deduces to A<int> and also initializes
A a4 = {0, i}; // OK, #2 deduces to A<int> and also initializes
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}; // error: #3 deduces to A<int&>
// and #1 & #2 result in same parameter constructors.
A a6{0, 1}; // OK, #4 deduces to A<int> and #2 initializes
A a7 = {0, i}; // error: #3 deduces to A<int&>
A a8{0, i}; // error: #3 deduces to A<int&>Использование типа члена typedef или шаблона алиаса в списке параметров конструктора или шаблона конструктора не делает соответствующий параметр неявного руководства по выводу контекстом, не подлежащим выводу.
template<class T>
struct B
{
template<class U>
using TA = T;
template<class U>
B(U, TA<U>); // #1
};
// Implicit deduction guide generated from #1 is the equivalent of
// template<class T, class U>
// B(U, T) -> B<T>;
// rather than
// template<class T, class U>
// B(U, typename B<T>::template TA<U>) -> B<T>;
// which would not have been deducible
B b{(int*)0, (char*)0}; // OK, deduces 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[]>;
// generated guide for unique_ptr_nonarray:
// from #1 (deduction of unique_ptr<T> from unique_ptr<A> yields T = A):
// template<class A>
// requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<A>>)
// auto F(A*) -> unique_ptr<A>;
// from #2 (deduction of unique_ptr<T[]> from unique_ptr<A> yields nothing):
// template<class T>
// requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<T[]>>)
// auto F(T*) -> unique_ptr<T[]>;
// where argument_of_unique_ptr_nonarray_is_deducible_from can be defined as
// 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>); };
// generated guide for unique_ptr_array:
// from #1 (deduction of unique_ptr<T> from unique_ptr<A[]> yields T = A[]):
// template<class A>
// requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>)
// auto F(A(*)[]) -> unique_ptr<A[]>;
// from #2 (deduction of unique_ptr<T[]> from unique_ptr<A[]> yields T = A):
// template<class A>
// requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>)
// auto F(A*) -> unique_ptr<A[]>;
// where argument_of_unique_ptr_array_is_deducible_from can be defined as
// 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>); };
// Use:
unique_ptr_nonarray p(new int); // deduced to unique_ptr<int>
// deduction guide generated from #1 returns unique_ptr<int>
// deduction guide generated from #2 returns unique_ptr<int[]>, which is ignored because
// argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<int[]>> is unsatisfied
unique_ptr_array q(new int[42]); // deduced to unique_ptr<int[]>
// deduction guide generated from #1 fails (cannot deduce A in A(*)[] from new int[42])
// deduction guide generated from #2 returns unique_ptr<int[]> | (с C++20) |
Примечания
Вывод аргументов шаблона класса выполняется только в том случае, если список шаблонов аргументов отсутствует. Если список шаблонов аргументов указан, вывод не выполняется.
std::tuple t1(1, 2, 3); // OK: deduction
std::tuple<int, int, int> t2(1, 2, 3); // OK: all arguments are provided
std::tuple<> t3(1, 2, 3); // Error: no matching constructor in tuple<>.
// No deduction performed.
std::tuple<int> t4(1, 2, 3); // Error| Вывод аргументов шаблона класса агрегатов обычно требует руководств по выводу, определённых пользователем: 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"}; // deduced to 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); // no deduction: X is the current X<T>
}
template<class Iter>
auto bar(Iter b, Iter e)
{
return X<typename Iter::value_type>(b, e); // must specify what we want
}
auto baz()
{
return ::X(0); // not the injected-class-name; deduced to be 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}; // uses #1 to deduce A<int> and initializes with #1
A b{a, 0}; // uses #2 (more specialized than #3) to deduce A<int> and initializes with #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, the copy deduction candidate A(A);
A x(1, 2, 3); // uses #3, generated from a non-template constructor
template<class T>
A(T) -> A<T>; // #6, less specialized than #5
A a(42); // uses #6 to deduce A<int> and #1 to initialize
A b = a; // uses #5 to deduce A<int> and #2 to initialize
template<class T>
A(A<T>) -> A<A<T>>; // #7, as specialized as #5
A b2 = a; // uses #7 to deduce A<A<int>> and #1 to initializeСсылка на параметр шаблона, не имеющий квалификаторов cv, с rvalue не является ссылкой-перенаправлением, если этот параметр является параметром шаблона класса:
template<class T>
struct A
{
template<class U>
A(T&&, U&&, int*); // #1: T&& is not a forwarding reference
// U&& is a forwarding reference
A(T&&, int*); // #2: T&& is not a forwarding reference
};
template<class T>
A(T&&, int*) -> A<T>; // #3: T&& is a forwarding reference
int i, *ip;
A a{i, 0, ip}; // error, cannot deduce from #1
A a0{0, 0, ip}; // uses #1 to deduce A<int> and #1 to initialize
A a2{i, ip}; // uses #3 to deduce A<int&> and #2 to initializeПри инициализации из одного аргумента типа, являющегося специализацией рассматриваемого шаблона класса, копирование по умолчанию предпочтительнее обертывания:
std::tuple t1{1}; //std::tuple<int>
std::tuple t2{t1}; //std::tuple<int>, not std::tuple<std::tuple<int>>
std::vector v1{1, 2}; // std::vector<int>
std::vector v2{v1}; // std::vector<int>, not std::vector<std::vector<int>> (P0702R1)
std::vector v3{v1, v2}; // std::vector<std::vector<int>>За исключением специального случая копирования по сравнению с обертыванием, сильная предпочтительность конструкторов списка инициализаторов в инициализации списком сохраняется.
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>, using deduction
auto p2 = std::make_tuple(1, 1.0); //std::tuple<int, double>, pre-C++17| Макрос проверки возможностей | Значение | Std | Функция |
|---|---|---|---|
__cpp_deduction_guides | 201703L | (C++17) | Вывод аргументов шаблона для шаблонов классов |
| 201907L | (C++20) | CTAD для агрегатов и псевдонимов |
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение, опубликованное в | Правильное поведение |
|---|---|---|---|
| CWG 2376 | C++17 | CTAD будет выполняться, даже если тип объявленной переменной отличается от шаблона класса, аргументы которого будут выведены | не выполнять CTAD в этом случае |
| CWG 2697 | C++20 | не было ясно, разрешено ли сокращенное синтаксическое представление шаблона функции в руководствах по выводу, определённых пользователем | запрещено |
| P0702R1 | C++17 | конструктор списка инициализаций может опередить кандидата копирования, что приведёт к обертыванию | этап инициализации списком пропускается при копировании |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/class_template_argument_deduction