클래스 템플릿 인자 추론

클래스 템플릿 인자 추론 (Class template argument deduction, CTAD)

std::pair p(2, 4.5);처럼 클래스 템플릿을 만들 때 <int, double>을 일일이 적지 않아도 되는 경우가 있어요. 클래스 템플릿을 인스턴스화하려면 모든 템플릿 인자를 알아야 하지만, 모든 인자를 지정해야 하는 건 아니에요. 아래 문맥들에서는 컴파일러가 초기화식의 타입에서 템플릿 인자를 추론해요. 이 페이지에서 그 클래스 템플릿 인자 추론(CTAD) 규칙을 살펴볼게요.

출처: cppreference

본문

인스턴스화를 위해 모든 템플릿 인자를 알아야 하지만, 모든 템플릿 인자를 지정할 필요는 없어요. 다음 문맥에서 컴파일러는 초기화식의 타입에서 템플릿 인자를 추론해요.

  • 변수와 변수 템플릿의 초기화를 지정하는 선언으로, 선언된 타입이 (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)>

(since C++20)

클래스 템플릿에 대한 추론 (Deduction for class templates)

암시적으로 생성되는 추론 가이드 (Implicitly-generated deduction guides)

함수형 캐스트나 변수 선언에서 타입 지정자가 기본 클래스 템플릿 C의 이름만으로 구성될 때(즉, 템플릿 인자 목록이 없을 때), 추론 후보는 다음과 같이 형성돼요.

  • C가 정의되어 있다면, 명명된 기본 템플릿에 선언된 각 생성자(또는 생성자 템플릿) Ci에 대해 가상의 함수 템플릿 Fi가 구성돼요. 이때 다음 조건을 모두 만족해요.
    • Fi의 템플릿 매개변수는 C의 템플릿 매개변수 뒤에(Ci가 생성자 템플릿이면)Ci의 템플릿 매개변수가 따르는 것이에요(기본 템플릿 인자도 포함).
    • Fi의 연관 제약은 C의 연관 제약과 Ci의 연관 제약의 결합이에요. (since C++20)
    • Fi의 매개변수 목록은 Ci의 매개변수 목록이에요.
    • Fi의 반환 타입은 <>로 둘러싼 클래스 템플릿의 템플릿 매개변수를 뒤따르는 C예요.
  • C가 정의되지 않았거나 어떤 생성자도 선언하지 않았다면, 가상의 생성자 C()에서 위처럼 파생된 추가 가상 함수 템플릿이 추가돼요.
  • 어떤 경우에도, 가상의 생성자 C(C)에서 위처럼 파생된 추가 가상 함수 템플릿이 추가되는데, 이를 복사 추론 후보(copy deduction candidate) 라고 해요.
  • 각 사용자 정의 추론 가이드 Gi에 대해 가상의 함수 또는 함수 템플릿 Fi가 구성돼요. 이때 다음 조건을 모두 만족해요.
    • Fi의 매개변수 목록은 Gi의 매개변수 목록이에요.
    • Fi의 반환 타입은 Gi의 단순 템플릿 식별자(simple template identifier)예요.
    • Gi가 템플릿 매개변수를 가진다면(구문 (2)) Fi는 함수 템플릿이고, 그 템플릿 매개변수 목록은 Gi의 템플릿 매개변수 목록이에요. 그렇지 않으면 Fi는 함수예요.

또한 다음 조건을 만족하면 집계 추론 후보(aggregate deduction candidate) 가 추가될 수 있어요.

  • C가 정의되어 있고, 어떤 의존적 베이스 클래스도 가상 함수나 가상 베이스 클래스를 가지지 않는다고 가정할 때 집계 타입(aggregate type)의 요구사항을 만족하고,
  • C에 대한 사용자 정의 추론 가이드가 없고,
  • 변수가 비어 있지 않은 초기화식 목록 arg1, arg2, ..., argn(지정 초기화식(designated initializer) 사용 가능)으로 초기화되는 경우.

집계 추론 후보의 매개변수 목록은 집계 요소 타입에서 다음과 같이 만들어진다.

  • eiargi에서 초기화될 (재귀적으로) 집계 요소라 하자. 이때 다음 조건을 만족하는 집계 요소에 대해서는 중괄호 생략(brace elision)을 고려하지 않아요.
    • 의존적 비-배열 타입을 가지거나,
    • 값 의존 경계를 가진 배열 타입이거나,
    • 의존적 배열 요소 타입을 가지면서 argi가 문자열 리터럴인 배열 타입.
  • C(또는 그 자신이 집계인 요소)가 팩 확장인 베이스를 가지면:
    • 팩 확장이 끝집합 element면 모든 남은 초기화식 요소와 일치하는 것으로 간주되고,
    • 그렇지 않으면 팩은 비어 있는 것으로 간주돼요.
  • 그런 ei가 없으면 집계 추론 후보는 추가되지 않아요.
  • 그렇지 않으면 집계 추론 후보의 매개변수 목록 T1, T2, ..., Tn을 다음과 같이 결정해요.
    • ei가 배열이고 argi가 중괄호 초기화 목록이면 Tiei의 선언된 타입에 대한 rvalue 참조예요.
    • ei가 배열이고 argi가 문자열 리터럴이면 Tiei의 const-한정 선언된 타입에 대한 lvalue 참조예요.
    • 그렇지 않으면 Tiei의 선언된 타입이에요.
    • 끝집합이 아닌 집계 요소라서 팩을 건너뛰었다면, 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>

(since C++20)

그런 다음 가상의 클래스 타입 객체의 초기화를 위해 템플릿 인자 추론과 오버로드 해석이 수행돼요. 그 가상 클래스의 생성자 시그니처는 (반환 타입을 제외하고) 가이드와 일치하며 오버로드 집합을 형성하고, 초기화식은 클래스 템플릿 인자 추론이 수행된 문맥에서 제공돼요. 단, 초기화 목록이 (cv 한정될 수 있는) U 타입의 단일 표현식으로 구성된 경우 리스트 초기화의 첫 단계(initializer-list 생성자 고려)를 생략해요. 여기서 UC의 전문화이거나 C의 전문화에서 파생된 클래스예요.

이 가상 생성자들은 가상 클래스 타입의 공개(public) 멤버예요. 가이드가 명시적(explicit) 생성자에서 형성됐다면 그 생성자들은 explicit이에요. 오버로드 해석이 실패하면 프로그램은 ill-formed예요. 그렇지 않으면 선택된 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};

사용자 정의 추론 가이드 (User-defined deduction guides)

사용자 정의 추론 가이드의 구문은 후행 반환 타입을 가진 함수(템플릿) 선언의 구문과 같아요. 다만 함수 이름 자리에 클래스 템플릿의 이름을 사용해요.

explicit (optional) template-name ( parameter-list ) -> simple-template-id requires-clause (optional) ;   (1)
template <template-parameter-list > requires-clause (optional) explicit (optional) template-name ( parameter-list ) -> simple-template-id requires-clause (optional) ;   (2)
  • template-parameter-list — 비어 있지 않은, 쉼표로 구분된 템플릿 매개변수 목록.
  • explicit — explicit 지정자.
  • template-name — 인자를 추론할 클래스 템플릿의 이름.
  • parameter-list — (비어 있을 수 있는) 매개변수 목록.
  • simple-template-id — 단순 템플릿 식별자.
  • requires-clause — (since C++20) requires 절.

사용자 정의 추론 가이드의 매개변수는 플레이스홀더 타입을 가질 수 없어요. 축약 함수 템플릿 구문은 허용되지 않아요. (since 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 생성자에서 형성된 암시적으로 생성되는 추론 가이드나 explicit으로 선언된 사용자 정의 추론 가이드에 대응하면 explicit이에요. 늘 그렇듯 그런 생성자는 복사 초기화(copy-initialization) 문맥에서 무시돼요.

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*>

별칭 템플릿에 대한 추론 (Deduction for alias templates)

함수형 캐스트나 변수 선언이 인자 목록 없이 별칭 템플릿 A의 이름을 타입 지정자로 사용하는데, AB<ArgList>의 별칭으로 정의되고, B의 스코프가 비-의존적이며, B가 클래스 템플릿이나 유사하게 정의된 별칭 템플릿인 경우, 추론은 클래스 템플릿과 같은 방식으로 진행돼요. 단 가이드는 B의 가이드에서 다음과 같이 생성돼요.

  • B의 각 가이드 f에 대해 템플릿 인자 추론을 사용해 B<ArgList>에서 f의 반환 타입의 템플릿 인자를 추론해요. 단 어떤 인자가 추론되지 않아도 추론이 실패하지는 않아요. 다른 이유로 추론이 실패하면 빈 추론된 템플릿 인자 집합으로 진행해요.
  • 위 추론 결과를 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[]>

(since C++20)

참고 (Notes)

클래스 템플릿 인자 추론은 템플릿 인자 목록이 없는 경우에만 수행돼요. 템플릿 인자 목록이 지정되면 추론은 일어나지 않아요.

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

집계(aggregate)의 클래스 템플릿 인자 추론은 보통 사용자 정의 추론 가이드를 요구해요.

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

(until C++20)

사용자 정의 추론 가이드는 템플릿일 필요가 없어요.

template<class T>
struct S
{
    S(T);
};
S(char const*) -> S<std::string>;

S s{"hello"}; // deduced to S<std::string>

클래스 템플릿의 스코프 안에서, 매개변수 목록 없이 템플릿의 이름은 주입된 클래스 이름(injected class name)이고 타입으로 사용할 수 있어요. 이 경우 클래스 인자 추론은 일어나지 않고 템플릿 매개변수를 명시적으로 제공해야 해요.

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>
    }
};

오버로드 해석에서 부분 순서(partial ordering)는 함수 템플릿이 사용자 정의 추론 가이드에서 생성됐는지보다 우선해요. 생성자에서 생성된 함수 템플릿이 사용자 정의 추론 가이드에서 생성된 것보다 더 전문화되어 있다면, 생성자에서 생성된 것이 선택돼요. 복사 추론 후보가 보통 감싸는(wrapping) 생성자보다 더 전문화되어 있으므로, 이 규칙은 복사가 일반적으로 감싸기보다 선호됨을 의미해요.

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

부분 순서를 포함한 이전의 동점 해소(tiebreaker) 규칙들이 두 후보 함수 템플릿을 구별하지 못하면 다음 규칙이 적용돼요.

  • 사용자 정의 추론 가이드에서 생성된 함수 템플릿이 생성자나 생성자 템플릿에서 암시적으로 생성된 것보다 선호돼요.
  • 복사 추론 후보가 생성자나 생성자 템플릿에서 암시적으로 생성된 다른 모든 함수 템플릿보다 선호돼요.
  • 비-템플릿 생성자에서 암시적으로 생성된 함수 템플릿이 생성자 템플릿에서 암시적으로 생성된 함수 템플릿보다 선호돼요.
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 참조는, 그 매개변수가 클래스 템플릿 매개변수라면 포워딩 참조(forwarding reference)가 아니에요.

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>>

복사 vs 감싸기의 특별한 경우를 제외하면, 리스트 초기화에서 initializer-list 생성자에 대한 강한 선호는 그대로 유지돼요.

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

피처 테스트 매크로 (Feature-test macro)

매크로 표준 피처
__cpp_deduction_guides 201703L (C++17) 클래스 템플릿에 대한 템플릿 인자 추론
__cpp_deduction_guides 201907L (C++20) 집계와 별칭에 대한 CTAD

결함 보고 (Defect reports)

다음 동작 변경 결함 보고는 이전에 발표된 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 initializer-list 생성자가 복사 추론 후보를 선점해서 감싸기를 만들 수 있었음 복사할 때 initializer-list 단계 생략