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

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

Если в приведении в стиле вызова функции или объявлении переменной в качестве спецификатора типа используется имя шаблона-синонима 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

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

Spec-Zone.ru

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