클래스 템플릿 인자 추론
클래스 템플릿 인자 추론 (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) 사용 가능)으로 초기화되는 경우.
집계 추론 후보의 매개변수 목록은 집계 요소 타입에서 다음과 같이 만들어진다.
ei를argi에서 초기화될 (재귀적으로) 집계 요소라 하자. 이때 다음 조건을 만족하는 집계 요소에 대해서는 중괄호 생략(brace elision)을 고려하지 않아요.- 의존적 비-배열 타입을 가지거나,
- 값 의존 경계를 가진 배열 타입이거나,
- 의존적 배열 요소 타입을 가지면서
argi가 문자열 리터럴인 배열 타입.
C(또는 그 자신이 집계인 요소)가 팩 확장인 베이스를 가지면:- 팩 확장이 끝집합 element면 모든 남은 초기화식 요소와 일치하는 것으로 간주되고,
- 그렇지 않으면 팩은 비어 있는 것으로 간주돼요.
- 그런
ei가 없으면 집계 추론 후보는 추가되지 않아요. - 그렇지 않으면 집계 추론 후보의 매개변수 목록
T1, T2, ..., Tn을 다음과 같이 결정해요.ei가 배열이고argi가 중괄호 초기화 목록이면Ti는ei의 선언된 타입에 대한 rvalue 참조예요.ei가 배열이고argi가 문자열 리터럴이면Ti는ei의 const-한정 선언된 타입에 대한 lvalue 참조예요.- 그렇지 않으면
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>
(since C++20)
그런 다음 가상의 클래스 타입 객체의 초기화를 위해 템플릿 인자 추론과 오버로드 해석이 수행돼요. 그 가상 클래스의 생성자 시그니처는 (반환 타입을 제외하고) 가이드와 일치하며 오버로드 집합을 형성하고, 초기화식은 클래스 템플릿 인자 추론이 수행된 문맥에서 제공돼요. 단, 초기화 목록이 (cv 한정될 수 있는) U 타입의 단일 표현식으로 구성된 경우 리스트 초기화의 첫 단계(initializer-list 생성자 고려)를 생략해요. 여기서 U는 C의 전문화이거나 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의 이름을 타입 지정자로 사용하는데, A가 B<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 단계 생략 |