Spec-Zone.ru › C++

Вывод шаблонов аргументов класса (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;
  • выражения new:
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), который называется кандидатом вывода для копирования.
  • Кроме того, если
  • C определено и удовлетворяет требованиям агрегатного типа с предположением, что любой зависимый базовый класс не имеет виртуальных функций или виртуальных базовых классов,
  • нет определённых руководств по выводу для C, и
  • переменная инициализируется непустым списком инициализаторов arg1, arg2, ..., argn (которые могут использовать инициализаторы с указанием),
может быть добавлен кандидат вывода для агрегата. Список параметров кандидата вывода для агрегата генерируется из типов элементов агрегата следующим образом:
  • Пусть ei — (возможно, рекурсивный) элемент агрегата, который будет инициализирован из argi, где
  • опускание фигурных скобок не рассматривается для любого элемента агрегата, у которого
    • зависимый тип, не являющийся массивом,
    • тип массива с зависимой границей, или
    • тип массива со зависимым типом элемента массива и argi является строковой литералом
  • если C (или его элемент, являющийся сам по себе агрегатом) имеет базовый класс, который является расширением пакета:
    • если расширение пакета является последним элементом агрегата, оно считается совпадающим со всеми оставшимися элементами списка инициализаторов;
    • в противном случае пакет считается пустым.
  • Если такого ei нет, кандидат вывода для агрегата не добавляется.
  • В противном случае определите список параметров T1, T2, ..., Tn кандидата вывода для агрегата следующим образом:
    • Если ei является массивом, и argi является списком инициализации , Ti является ссылкой на временный объект типа, объявленного для ei.
    • Если ei является массивом и argi является строковой литералом, Ti является ссылкой на константный тип, объявленный для ei.
    • В противном случае Ti является типом, объявленным для ei.
    • Если пакет был пропущен, потому что это не последний элемент агрегата, дополнительный пакет параметров в форме Pj ... вставляется в исходную позицию элемента агрегата. (Это, как правило, приведёт к отказу вывода.)
    • Если пакет является последним элементом агрегата, последовательность параметров, соответствующих ему, заменяется одним параметром в форме Tn ....
Кандидат вывода для агрегата — вымышленный шаблон функции, полученный как указано выше из гипотетического конструктора C(T1, T2, ..., Tn).
При выводе аргументов шаблона для кандидата вывода агрегата количество элементов в хвостовом пакете параметров определяется только из количества оставшихся аргументов функции, если это не определено каким-либо другим способом.
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*>

Вывод для шаблонов псевдонимов

Когда функционоподобное приведение или объявление переменной использует имя шаблона псевдонима A без списка аргументов в качестве спецификатора типа, где A определено как псевдоним B<ArgList>, область действия B не зависит, а B является либо шаблоном класса, либо аналогично определённым шаблоном псевдонима, вывод будет выполняться так же, как для шаблонов классов, за исключением того, что руководства генерируются из руководств B, следующим образом:

  • Для каждого руководства f B, выведите шаблоны аргументов возвращаемого типа f из B<ArgList> с помощью вывода шаблонов аргументов, за исключением того, что вывод не завершается ошибкой, если некоторые аргументы не выводятся. Если вывод завершается ошибкой по другой причине, продолжайте с пустым набором выведенных шаблонов аргументов.
  • Подставьте результат вышеуказанного вывода в f, если подстановка завершается ошибкой, руководство не создаётся; в противном случае, пусть g обозначает результат подстановки, формируется руководство f', такое что
    • Типы параметров и возвращаемый тип f' такие же, как у g
    • Если f является шаблоном, f' является шаблонной функцией, список шаблонов параметров которой состоит из всех шаблонов параметров A (включая их шаблоны аргументов по умолчанию), которые появляются в вышеуказанных выводах или (рекурсивно) в их шаблонах аргументов по умолчанию, за которыми следуют параметры шаблона f которые не были выведены (включая их шаблоны аргументов по умолчанию); в противном случае (f не является шаблоном), f' — это функция
    • Связанные ограничения f' являются конъюнкцией связанных ограничений g и ограничения, которое выполняется тогда и только тогда, когда аргументы A могут быть выведены из типа результата
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

Spec-Zone.ru

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