Вывод аргументов шаблона класса (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(включая аргументы шаблона по умолчанию).
| (начиная с C++20) |
- Список параметров
Fiсовпадает со списком параметровCi. - Типом возвращаемого значения
FiявляетсяC, за которым следуют параметры шаблона шаблона класса, заключённые в<>. - Если
Cне определён или не объявляет ни одного конструктора, добавляется ещё один фиктивный шаблон функции, полученный описанным выше способом из гипотетического конструктораC(). - В любом случае добавляется ещё один фиктивный шаблон функции, полученный описанным выше способом из гипотетического конструктора
C(C)и называемый кандидатом вывода копирования. - Для каждого пользовательского руководства по выводу
Giсоздаётся фиктивная функция или шаблон функцииFi, удовлетворяющая всем следующим условиям: - Список параметров
Fiсовпадает со списком параметровGi. - Типом возвращаемого значения
Fiявляется простой идентификатор шаблонаGi. - Если
Giимеет параметры шаблона (синтаксис (2)),Fiявляется шаблоном функции, а его список параметров шаблона совпадает со списком параметров шаблонаGi. В противном случаеFiявляется функцией.
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 (необязательно) 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 |
| Параметры пользовательских руководств по выводу не могут иметь типы-заполнители: синтаксис сокращённого шаблона функции не допускается. | (начиная с 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&>
// Note: check https://github.com/cplusplus/CWG/issues/647, claiming that
// examples a7 and a8 are incorrect, to be possibly replaced as
//A a7 = {0, i}; // error: #2 and #3 both match, overload resolution fails
//A a8{i,i}; // error: #3 deduces to A<int&>,
// // #1 and #2 declare same constructor
Использование 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
Rvalue-ссылка на параметр шаблона без квалификаторов cv не является универсальной ссылкой, если этот параметр является параметром шаблона класса:
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
| Макрос проверки поддержки функции | Значение | Стандарт | Функция |
|---|---|---|---|
__cpp_deduction_guides | 201703L | (C++17) | Вывод аргументов шаблона для шаблонов классов |
201907L | (C++20) | CTAD для агрегатов и шаблонов-синонимов |
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.
| DR | Применён к | Опубликованное поведение | Корректное поведение |
|---|---|---|---|
| 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 2707 сделало синтаксис руководств по выводу несогласованным с синтаксисом объявления функции | синтаксис скорректирован |
| P0702R1 | C++17 | конструктор со списком инициализации мог опередить кандидата вывода копирования, что приводило к обёртыванию | фаза со списком инициализации пропускается при копировании |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/class_template_argument_deduction