Template argument deduction
함수 템플릿 을 인스턴스화하려면 모든 템플릿 인자가 알려져야 하지만, 모든 템플릿 인자를 명시적으로 지정할 필요는 없습니다. 가능한 경우 컴파일러는 함수 인자로부터 누락된 템플릿 인자를 추론합니다. 이는 함수 호출이 시도될 때, 함수 템플릿의 주소가 취해질 때, 그리고 기타 상황 에서 발생합니다:
template<typename To, typename From> To convert(From f); void g(double d) { int i = convert<int>(d); // convert<int, double>(double)를 호출합니다 char c = convert<char>(d); // convert<char, double>(double)를 호출합니다 int(*ptr)(float) = convert; // convert<int, float>(float)를 인스턴스화하고 // 그 주소를 ptr에 저장합니다 }
이 메커니즘은 템플릿 연산자를 사용할 수 있게 합니다. 연산자에 대한 템플릿 인수를 지정하는 구문이 함수 호출 표현식으로 재작성하는 방법 외에는 존재하지 않기 때문입니다:
템플릿 인수 추론은 함수 템플릿 이름 조회 (여기에는 인수 의존 조회 가 포함될 수 있음) 이후에 발생하며, 템플릿 인수 치환 (여기에는 SFINAE 가 포함될 수 있음) 및 오버로드 해결 이전에 수행됩니다.
|
클래스 템플릿의 이름이 생성되는 객체의 타입으로 사용될 때에도 템플릿 인자 추론이 수행됩니다: std::pair p(2, 4.5); std::tuple t(4, 3, 2.5); std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); auto lck = std::lock_guard(foo.mtx); std::lock_guard lck2(foo.mtx, ul); 클래스 템플릿에 대한 템플릿 인자 추론은 선언문과 명시적 캐스트 표현식에서 발생합니다; 자세한 내용은 클래스 템플릿 인자 추론 을 참조하십시오. |
(C++17부터) |
목차 |
함수 호출에서의 추론
템플릿 인수 추론은 각 매개변수
P
에 대입되었을 때 아래에 나열된 조정 후의 인수
A
의 타입과 동일한
추론된
타입
A
를 생성할 수 있는 템플릿 인수들(타입 템플릿 매개변수
T
i에 대한 타입, 템플릿 템플릿 매개변수
TT
i에 대한 템플릿, 상수 템플릿 매개변수
I
i에 대한 값)을 결정하려고 시도합니다.
여러 개의 매개변수가 있는 경우, 각
P
/
A
쌍은 개별적으로 추론되며, 추론된 템플릿 인수들은 결합됩니다. 만약 어떤
P
/
A
쌍에 대해 추론이 실패하거나 모호한 경우, 또는 서로 다른 쌍들이 서로 다른 추론된 템플릿 인수를 생성하는 경우, 또는 어떤 템플릿 인수가 추론되지도 않고 명시적으로 지정되지도 않은 상태로 남아 있는 경우, 컴파일이 실패합니다.
|
template<class T> void f(std::initializer_list<T>); f({1, 2, 3}); // P = std::initializer_list<T>, A = {1, 2, 3} // P'1 = T, A'1 = 1: deduced T = int // P'2 = T, A'2 = 2: deduced T = int // P'3 = T, A'3 = 3: deduced T = int // OK: deduced T = int f({1, "abc"}); // P = std::initializer_list<T>, A = {1, "abc"} // P'1 = T, A'1 = 1: deduced T = int // P'2 = T, A'2 = "abc": deduced T = const char* // error: deduction fails, T is ambiguous
template<class T, int N> void h(T const(&)[N]); h({1, 2, 3}); // deduced T = int, deduced N = 3 template<class T> void j(T const(&)[3]); j({42}); // deduced T = int, array bound is not a parameter, not considered struct Aggr { int i; int j; }; template<int N> void k(Aggr const(&)[N]); k({1, 2, 3}); // error: deduction fails, no conversion from int to Aggr k({{1}, {2}, {3}}); // OK: deduced N = 3 template<int M, int N> void m(int const(&)[M][N]); m({{1, 2}, {3, 4}}); // deduced M = 2, deduced N = 2 template<class T, int N> void n(T const(&)[N], T); n({{1}, {2}, {3}}, Aggr()); // deduced T = Aggr, deduced N = 3
parameter pack
이 마지막
template<class... Types> void f(Types&...); void h(int x, float& y) { const int z = x; f(x, y, z); // P = Types&..., A1 = x: deduced first member of Types... = int // P = Types&..., A2 = y: deduced second member of Types... = float // P = Types&..., A3 = z: deduced third member of Types... = const int // calls f<int, float, const int> }
|
(since C++11) |
만약
P
가 함수 타입, 함수 포인터 타입, 또는 멤버 함수 포인터 타입이고
A
가 함수 템플릿을 포함하지 않는
오버로드된 함수 집합
인 경우, 각 오버로드에 대해 템플릿 인자 추론이 시도됩니다. 단 하나만 성공하면 그 성공한 추론이 사용됩니다. 하나도 성공하지 않거나 둘 이상이 성공하면, 템플릿 매개변수는 비추론 문맥입니다 (아래 참조):
template<class T> int f(T(*p)(T)); int g(int); int g(char); f(g); // P = T(*)(T), A = 오버로드 집합 // P = T(*)(T), A1 = int(int): 추론된 T = int // P = T(*)(T), A2 = int(char): T 추론 실패 // 하나의 오버로드만 작동하므로 추론 성공
deduction이 시작되기 전에, 다음과 같은 조정이
P
와
A
에 대해 이루어집니다:
P
가 참조 타입이 아닌 경우,
A
가 배열 타입인 경우,
A
는 배열-대-포인터 변환에서 얻은 포인터 타입으로 대체됩니다;
A
가 함수 타입인 경우,
A
는 함수-대-포인터 변환에서 얻은 포인터 타입으로 대체됩니다;
A
가 cv 한정자(cv-qualified) 타입인 경우, 최상위 cv 한정자는 추론 시 무시됩니다:
template<class T> void f(T); int a[3]; f(a); // P = T, A = int[3], adjusted to int*: deduced T = int* void b(int); f(b); // P = T, A = void(int), adjusted to void(*)(int): deduced T = void(*)(int) const int c = 13; f(c); // P = T, A = const int, adjusted to int: deduced T = int
P
가 cv-한정된 타입인 경우, 최상위 cv 한정자는 추론 시 무시됩니다.
P
가 참조 타입인 경우, 참조된 타입이 추론에 사용됩니다.
P
가 cv-unqualified 템플릿 매개변수에 대한 rvalue 참조(소위
forwarding references
)이고, 해당 함수 호출 인자가 lvalue인 경우,
A
대신
A
에 대한 lvalue 참조 타입이 추론에 사용됩니다(참고: 이것은
std::forward
의 동작 기반입니다
. 참고:
class template argument deduction
에서 클래스 템플릿의 템플릿 매개변수는 절대 forwarding reference가 아닙니다
(since C++17)
):
template<class T> int f(T&&); // P is an rvalue reference to cv-unqualified T (forwarding reference) template<class T> int g(const T&&); // P is an rvalue reference to cv-qualified T (not special) int main() { int i; int n1 = f(i); // argument is lvalue: calls f<int&>(int&) (special case) int n2 = f(0); // argument is not lvalue: calls f<int>(int&&) // int n3 = g(i); // error: deduces to g<int>(const int&&), which // cannot bind an rvalue reference to an lvalue }
이러한 변환 후에, 아래에 설명된 추론 과정(참조:
deduction from a type
)이 진행되며, 추론된
A
(즉, 위에서 나열된 조정과 추론된 템플릿 매개변수의 치환 이후의
P
)가
변환된
A
, 즉 위에서 나열된 조정 이후의
A
와 동일하게 만들 템플릿 인자를 찾으려 시도합니다.
일반적인 공제가
P
와
A
에서 실패할 경우, 다음과 같은 대안들이 추가로 고려됩니다:
P
가 참조 타입인 경우, 추론된
A
(즉, 참조에 의해 참조되는 타입)는 변환된
A
보다 더 많은 cv-qualified일 수 있습니다:
template<typename T> void f(const T& t); bool a = false; f(a); // P = const T&, adjusted to const T, A = bool: // deduced T = bool, deduced A = const bool // deduced A is more cv-qualified than A
A
는 추론된
A
로
한정 변환
또는 함수 포인터 변환
(C++17부터)
을 통해 변환될 수 있는 다른 포인터 또는 멤버 포인터 타입일 수 있습니다:
template<typename T> void f(const T*); int* p; f(p); // P = const T*, A = int*: // deduced T = int, deduced A = const int* // qualification conversion applies (from int* to const int*)
P
가 클래스이고
P
가
simple-template-id
형태를 가진다면, 변환된
A
는 추론된
A
의 파생 클래스일 수 있습니다. 마찬가지로,
P
가
simple-template-id
형태의 클래스에 대한 포인터라면, 변환된
A
는 추론된
A
가 가리키는 클래스의 파생 클래스를 가리키는 포인터일 수 있습니다:
template<class T> struct B {}; template<class T> struct D : public B<T> {}; template<class T> void f(B<T>&) {} void f() { D<int> d; f(d); // P = B<T>&, adjusted to P = B<T> (a simple-template-id), A = D<int>: // deduced T = int, deduced A = B<int> // A is derived from deduced A }
비추론 컨텍스트
다음 경우들에서,
P
를 구성하는 데 사용되는 타입, 템플릿, 그리고 상수들은 템플릿 인자 추론에 참여하지 않으며, 대신
사용
되는 템플릿 인자들은 다른 곳에서 추론되었거나 명시적으로 지정된 것들입니다. 만약 템플릿 매개변수가 비추론 문맥에서만 사용되고 명시적으로 지정되지 않으면, 템플릿 인자 추론은 실패합니다.
// 특정 인수를 추론에서 제외하기 위해 자주 사용되는 identity 템플릿 // (C++20부터 std::type_identity로 사용 가능) template<typename T> struct identity { typedef T type; }; template<typename T> void bad(std::vector<T> x, T value = 1); template<typename T> void good(std::vector<T> x, typename identity<T>::type value = 1); std::vector<std::complex<double>> x; bad(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>> // P1/A1: 추론된 T = std::complex<double> // P2 = T, A2 = double // P2/A2: 추론된 T = double // 오류: 추론 실패, T가 모호함 good(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>> // P1/A1: 추론된 T = std::complex<double> // P2 = identity<T>::type, A2 = double // P2/A2: P2에서 ::의 왼쪽에 T가 있으므로 P1/A1에 의해 추론된 T를 사용 // 성공: T = std::complex<double>
|
2)
pack indexing specifier
또는
pack indexing expression
:
template<typename... Ts> void f(Ts...[0], std::tuple<Ts...>); f(3, std::tuple(5, 'A')); // P2 = std::tuple<Ts...>, A2 = std::tuple<int, char> // P2/A2: deduced first member of Ts... = int // P2/A2: deduced second member of Ts... = char // P1 = Ts...[0], A1 = int: Ts...[0] is in non-deduced context |
(C++26부터) |
|
3)
decltype
지정자의 표현식:
template<typename T> void f(decltype(*std::declval<T>()) arg); int n; f<int*>(n); // P = decltype(*declval<T>()), A = int: T is in non-deduced context |
(C++11부터) |
template<std::size_t N> void f(std::array<int, 2 * N> a); std::array<int, 10> a; f(a); // P = std::array<int, 2 * N>, A = std::array<int, 10>: // 2 * N is non-deduced context, N cannot be deduced // note: f(std::array<int, N> a) would be able to deduce N
template<typename T, typename F> void f(const std::vector<T>& v, const F& comp = std::less<T>()); std::vector<std::string> v(3); f(v); // P1 = const std::vector<T>&, A1 = std::vector<std::string> lvalue // P1/A1 deduced T = std::string // P2 = const F&, A2 = std::less<std::string> rvalue // P2 is non-deduced context for F (template parameter) used in the // parameter type (const F&) of the function parameter comp, // that has a default argument that is being used in the call f(v)
P
의
A
가 함수 또는 오버로드 집합인 경우, 둘 이상의 함수가
P
와 일치하거나 어떤 함수도
P
와 일치하지 않거나 오버로드 집합에 하나 이상의 함수 템플릿이 포함되는 경우:
P
가 중괄호 초기화 목록인
A
를 가지지만,
P
가
std::initializer_list
나 이에 대한 참조(가능한 cv-qualified), 또는 배열에 대한 참조가 아닌 경우}}:
template<class T> void g1(std::vector<T>); template<class T> void g2(std::vector<T>, T x); g1({1, 2, 3}); // P = std::vector<T>, A = {1, 2, 3}: T가 비추론 문맥에 있음 // 오류: T가 명시적으로 지정되지 않았거나 다른 P/A에서 추론되지 않음 g2({1, 2, 3}, 10); // P1 = std::vector<T>, A1 = {1, 2, 3}: T가 비추론 문맥에 있음 // P2 = T, A2 = int: T = int로 추론됨
|
8)
매개변수 목록의 끝에 위치하지 않은 파라미터 팩인 매개변수
P
:
template<class... Ts, class T> void f1(T n, Ts... args); template<class... Ts, class T> void f2(Ts... args, T n); f1(1, 2, 3, 4); // P1 = T, A1 = 1: 추론된 T = int // P2 = Ts..., A2 = 2, A3 = 3, A4 = 4: 추론된 Ts = [int, int, int] f2(1, 2, 3, 4); // P1 = Ts...: Ts는 비추론 문맥
9)
매개변수
P
내에 나타나며, 템플릿 매개변수 목록의 가장 끝에 위치하지 않은 팩 확장을 포함하는 템플릿 매개변수 목록:
template<int...> struct T {}; template<int... Ts1, int N, int... Ts2> void good(const T<N, Ts1...>& arg1, const T<N, Ts2...>&); template<int... Ts1, int N, int... Ts2> void bad(const T<Ts1..., N>& arg1, const T<Ts2..., N>&); T<1, 2> t1; T<1, -1, 0> t2; good(t1, t2); // P1 = const T<N, Ts1...>&, A1 = T<1, 2>: // 추론된 N = 1, 추론된 Ts1 = [2] // P2 = const T<N, Ts2...>&, A2 = T<1, -1, 0>: // 추론된 N = 1, 추론된 Ts2 = [-1, 0] bad(t1, t2); // P1 = const T<Ts1..., N>&, A1 = T<1, 2>: // <Ts1..., N>는 비추론 문맥 // P2 = const T<Ts2..., N>&, A2 = T<1, -1, 0>: // <Ts2..., N>는 비추론 문맥 |
(C++11부터) |
P
에 대해, 주요 배열 경계:
template<int i> void f1(int a[10][i]); template<int i> void f2(int a[i][20]); // P = int[i][20], array type template<int i> void f3(int (&a)[i][20]); // P = int(&)[i][20], reference to array void g() { int a[10][20]; f1(a); // OK: deduced i = 20 f1<20>(a); // OK f2(a); // error: i is non-deduced context f2<10>(a); // OK f3(a); // OK: deduced i = 10 f3<10>(a); // OK }
어떤 경우든, 타입 이름의 일부가 비추론(non-deduced) 영역이면 전체 타입 이름이 비추론 문맥이 됩니다. 그러나 복합 타입은 추론된 타입 이름과 비추론 타입 이름을 모두 포함할 수 있습니다. 예를 들어,
A
<
T
>
::
B
<
T2
>
에서
T
는 규칙 #1(중첩 이름 지정자)에 의해 비추론되고,
T2
는 동일한 타입 이름의 일부이므로 비추론됩니다. 하지만
void
(
*
f
)
(
typename
A
<
T
>
::
B
, A
<
T
>
)
에서는
A
<
T
>
::
B
의
T
는 동일한 규칙으로 인해 비추론되는 반면,
A
<
T
>
의
T
는 추론됩니다.
타입으로부터의 추론
하나 이상의 타입 템플릿 매개변수
T
i, 템플릿 템플릿 매개변수
TT
i, 또는 상수 템플릿 매개변수
I
i에 의존하는 함수 매개변수
P
와 해당 인자
A
가 주어졌을 때,
P
가 다음 형태 중 하나를 가지는 경우 추론이 발생합니다:
|
이 섹션은 불완전합니다
이유: 아마도 간단한 예제가 포함된 표일 수 있음 |
-
cv(선택적)T; -
T*; -
T&;
|
(C++11부터) |
-
T(선택적)[I(선택적)];
|
(C++17 이전) |
|
(C++17 이후) |
-
T(선택적)U(선택적)::*; -
TT(선택적)<T>; -
TT(선택적)<I>; -
TT(선택적)<TU>; -
TT(선택적)<>.
위의 형태에서,
-
T(선택적) 또는U(선택적) 은 이러한 규칙을 재귀적으로 만족하는 타입이나 parameter-type-list 를 나타내며, 이는P또는A에서 비추론된 컨텍스트이거나,P와A에서 동일한 비의존 타입입니다. -
TT(선택적) 또는TU(선택적) 은 클래스 템플릿이나 템플릿 템플릿 매개변수를 나타냅니다. -
I(선택적) 은I인 표현식이거나,P또는A에서 값에 의존적이거나,P와A에서 동일한 상수 값을 가지는 표현식을 나타냅니다.
|
(C++17부터) |
만약
P
가 템플릿 매개변수 목록
<T>
또는
<I>
를 포함하는 형태 중 하나를 가지면, 해당 템플릿 인수 목록의 각 요소
P
i는 해당하는 템플릿 인수
A
i와 매칭됩니다. 만약 마지막
P
i가 팩 확장(pack expansion)이면, 그 패턴은
A
의 템플릿 인수 목록에 남아 있는 각 나머지 인수와 비교됩니다. 달리 추론되지 않는 후행 매개변수 팩(parameter pack)은 빈 매개변수 팩으로 추론됩니다.
만약
P
가 함수 매개변수 목록
(T)
을 포함하는 형태 중 하나를 가지면, 해당 목록의 각 매개변수
P
i는
A
의 함수 매개변수 목록에서 대응하는 인수
A
i와 비교됩니다. 만약 마지막
P
i가 팩 확장(pack expansion)이면, 그 선언자(declarator)는
A
의 매개변수 타입 목록에 남아있는 각
A
i와 비교됩니다.
폼은 중첩되고 재귀적으로 처리될 수 있습니다:
-
X
<
int
>
(
*
)
(
char
[
6
]
)
는
T*의 예시이며, 여기서T는 X < int > ( char [ 6 ] ) 입니다;
|
(C++17 이전) |
|
(C++17 이후) |
-
X
<
int
>
는
TT(선택적)<T>의 예시입니다. 여기서TT는X이고T는 int 입니다. -
char
[
6
]
는
T(선택적)[I(선택적)]의 예시입니다. 여기서T는 char 이고I는 std:: size_t ( 6 ) 입니다.
|
타입 템플릿 인자는 상수 템플릿 인자의 타입으로부터 추론될 수 없습니다: template<typename T, T i> void f(double a[10][i]); double v[10][20]; f(v); // P = double[10][i], A = double[10][20]: // i can be deduced to equal 20 // but T cannot be deduced from the type of i |
(C++17까지) |
|
종속 타입으로 선언된 상수 템플릿 매개변수 P에 해당하는 인자의 값이 표현식으로부터 추론될 때, P의 타입에 있는 템플릿 매개변수들은 값의 타입으로부터 추론됩니다. template<long n> struct A {}; template<class T> struct C; template<class T, T n> struct C<A<n>> { using Q = T; }; typedef long R; typedef C<A<2>>::Q R; // OK: T was deduced to long // from the template argument value in the type A<2> template<auto X> class bar {}; template<class T, T n> void f(bar<n> x); f(bar<3>{}); // OK: T was deduced to int (and n to 3) // from the template argument value in the type bar<3>
template<class T, T i> void f(int (&a)[i]); int v[10]; f(v); // OK: T is std::size_t
함수 타입의
noexcept
(
B
)
지정자에서
template<bool> struct A {}; template<auto> struct B; template<auto X, void (*F)() noexcept(X)> struct B<F> { A<X> ax; }; void f_nothrow() noexcept; B<f_nothrow> bn; // OK: X is deduced as true and the type of X is deduced as bool. |
(C++17부터) |
함수 템플릿의 상수 템플릿 매개변수가 함수 매개변수(역시 템플릿인)의 템플릿 매개변수 목록에서 사용되고, 해당 템플릿 인자가 추론되는 경우, 추론된 템플릿 인자의 타입(해당 인자를 포함하는 템플릿 매개변수 목록에 지정된 대로, 즉 참조가 보존됨)은 상수 템플릿 매개변수의 타입과 정확히 일치해야 합니다. 단, cv 한정자는 제거되며, 템플릿 인자가 배열 경계에서 추론되는 경우는 예외입니다—그 경우에는 모든 정수 타입이 허용됩니다, 심지어 bool 타입이라도 그것이 항상 true 가 될 것임에도 불구하고:
template<int i> class A {}; template<short s> void f(A<s>); // 상수 템플릿 매개변수의 타입은 short void k1() { A<1> a; // a의 상수 템플릿 매개변수 타입은 int f(a); // P = A<(short)s>, A = A<(int)1> // 오류: 추론된 상수 템플릿 인자가 해당 템플릿 인자와 동일한 타입을 가지지 않음 f<1>(a); // OK: 템플릿 인자가 추론되지 않음, // 이는 f<(short)1>(A<(short)1>)를 호출함 } template<int&> struct X; template<int& R> void k2(X<R>&); int n; void g(X<n> &x) { k2(x); // P = X<R>, A = X<n> // 매개변수 타입은 int& // 인자 타입은 struct X의 템플릿 선언에서 int& // OK (CWG 2091 적용): R이 n을 참조하도록 추론됨 }
함수 기본 인자의 타입으로부터 타입 템플릿 매개변수를 추론할 수 없습니다:
template<typename T> void f(T = 5, T = 7); void g() { f(1); // OK: f<int>(1, 7) 호출 f(); // 오류: T를 추론할 수 없음 f<int>(); // OK: f<int>(5, 7) 호출 }
템플릿 템플릿 매개변수의 추론은 함수 호출에 사용된 템플릿 특수화에서 사용된 타입을 활용할 수 있습니다:
template<template<typename> class X> struct A {}; // A는 TT 매개변수를 갖는 템플릿입니다 template<template<typename> class TT> void f(A<TT>) {} template<class T> struct B {}; A<B> ab; f(ab); // P = A<TT>, A = A<B>: TT가 B로 추론됨, f(A<B>) 호출
다른 컨텍스트
함수 호출 및 연산자 표현식 외에도, 템플릿 인수 추론은 다음 상황에서 사용됩니다:
auto 타입 추론템플릿 인자 추론은 변수 선언에서 사용되며, 변수의 초기화식으로부터 auto 지정자 의 의미를 추론할 때 적용됩니다.
매개변수
const auto& x = 1 + 2; // P = const U&, A = 1 + 2: // same rules as for calling f(1 + 2) where f is // template<class U> void f(const U& u) // deduced U = int, the type of x is const int& auto l = {13}; // P = std::initializer_list<U>, A = {13}: // deduced U = int, the type of l is std::initializer_list<int> 직접 목록 초기화에서(복사 목록 초기화에서는 아님), 중괄호 초기화 목록으로부터 auto 의 의미를 추론할 때, 중괄호 초기화 목록은 단 하나의 요소만을 포함해야 하며, auto의 타입은 그 요소의 타입이 됩니다: auto x1 = {3}; // x1 is std::initializer_list<int> auto x2{1, 2}; // error: not a single element auto x3{3}; // x3 is int // (before N3922 x2 and x3 were both std::initializer_list<int>) |
(C++11부터) |
auto 반환 함수템플릿 인자 추론은 함수의 반환문으로부터 함수의 반환 타입에 있는 auto 지정자의 의미를 추론할 때 함수 선언에서 사용됩니다.
auto 반환 함수의 경우, 매개변수
auto f() { return 42; } // P = auto, A = 42: // deduced U = int, the return type of f is int 이러한 함수에 여러 개의 반환문이 있는 경우, 각 반환문에 대해 추론이 수행됩니다. 결과로 나온 모든 타입은 동일해야 하며 실제 반환 타입이 됩니다.
이러한 함수에 반환문이 없는 경우, 추론 시
참고: 변수 및 함수 선언에서 decltype ( auto ) 자리 표시자의 의미는 템플릿 인자 추론을 사용하지 않습니다. |
(C++14부터) |
오버로드 해결
템플릿 인수 추론은 후보 템플릿 함수로부터 특수화를 생성할 때
오버로드 해결
과정에서 사용됩니다.
P
와
A
는 일반 함수 호출에서와 동일합니다:
std::string s; std::getline(std::cin, s); // "std::getline"은 4개의 함수 템플릿을 지칭하며, // 그 중 2개가 후보 함수입니다 (올바른 매개변수 개수) // 첫 번째 후보 템플릿: // P1 = std::basic_istream<CharT, Traits>&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // 추론으로 타입 템플릿 매개변수 CharT, Traits, Allocator 결정 // 특수화 std::getline<char, std::char_traits<char>, std::allocator<char>> // 두 번째 후보 템플릿: // P1 = std::basic_istream<CharT, Traits>&&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // 추론으로 타입 템플릿 매개변수 CharT, Traits, Allocator 결정 // 특수화 std::getline<char, std::char_traits<char>, std::allocator<char>> // 오버로드 해결은 lvalue std::cin에서의 참조 바인딩을 평가하고 // 두 후보 특수화 중 첫 번째를 선택합니다
추론이 실패하거나, 추론은 성공했지만 생성된 특수화가 유효하지 않은 경우(예: 매개변수가 클래스나 열거형 타입이 아닌 오버로드된 연산자), 해당 특수화는 오버로드 집합에 포함되지 않으며, 이는 SFINAE 와 유사합니다.
오버로드 집합의 주소
템플릿 인수 추론은 함수 템플릿을 포함한 오버로드 집합의 주소 를 취할 때 사용됩니다.
함수 템플릿의 함수 타입은
P
입니다.
대상 타입
은
A
의 타입입니다:
std::cout << std::endl; // std::endl은 함수 템플릿을 지칭함 // endl의 타입 P = // std::basic_ostream<CharT, Traits>& (std::basic_ostream<CharT, Traits>&) // operator<< 매개변수 A = // std::basic_ostream<char, std::char_traits<char>>& (*)( // std::basic_ostream<char, std::char_traits<char>>& // ) // (operator<<의 다른 오버로드들은 적합하지 않음) // 추론이 타입 템플릿 매개변수 CharT와 Traits를 결정함
이 경우 추론에 추가 규칙이 적용됩니다: 함수 매개변수
P
i와
A
i를 비교할 때, 만약
P
i가 cv-unqualified 템플릿 매개변수에 대한 rvalue 참조("전달 참조")이고 해당하는
A
i가 lvalue 참조인 경우,
P
i는 템플릿 매개변수 타입으로 조정됩니다(T&&는 T가 됨).
|
함수 템플릿의 반환 타입이 플레이스홀더( auto 또는 decltype ( auto ) )인 경우, 해당 반환 타입은 비추론 문맥(non-deduced context)이며 인스턴스화로부터 결정됩니다. |
(since C++14) |
부분 순서
템플릿 인수 추론은 오버로드된 함수 템플릿의 부분 순서 지정 동안 사용됩니다.
|
이 섹션은 불완전합니다
이유: mini-example |
변환 함수 템플릿
템플릿 인수 추론은 사용자 정의 변환 함수 템플릿 인수를 선택할 때 사용됩니다.
A
는 변환 결과로 요구되는 타입입니다.
P
는 변환 함수 템플릿의 반환 타입입니다. 만약
P
가 참조 타입인 경우, 이 섹션의 다음 부분들에서는
P
대신 참조된 타입이 사용됩니다.
만약
A
가 참조 타입이 아닌 경우:
P
가 배열 타입인 경우, 배열-포인터 변환을 통해 얻은 포인터 타입이
P
대신 사용됩니다;
P
가 함수 타입인 경우, 함수-포인터 변환을 통해 얻은 함수 포인터 타입이
P
대신 사용됩니다;
P
가 cv-한정된 경우, 최상위 cv-한정자는 무시됩니다.
만약
A
가 cv-qualified라면, 최상위 cv-qualifiers는 무시됩니다. 만약
A
가 참조 타입이라면, 참조된 타입이 deduction에 사용됩니다.
P
와
A
로부터의 일반적인 추론(위에서 설명한 대로)이 실패할 경우, 다음과 같은 대안들이 추가로 고려됩니다:
A
가 참조 타입인 경우,
A
는 추론된
A
보다 더 많은 cv 한정자를 가질 수 있습니다;
A
가 포인터 또는 멤버 포인터 타입인 경우, 추론된
A
는 qualification conversion을 통해
A
로 변환 가능한 모든 포인터가 허용됩니다:
struct C { template<class T> operator T***(); }; C c; const int* const* const* p1 = c; // P = T***, A = const int* const* const* // 일반 함수 호출 추론은 // template<class T> void f(T*** p)가 const int* const* const* 타입의 인수로 // 호출된 것처럼 수행되며 실패합니다 // 변환 함수에 대한 추가 추론이 T = int로 결정됩니다 // (추론된 A는 int***이며, const int* const* const*로 변환 가능합니다)
|
c)
만약
A
가 함수 포인터 타입인 경우, 추론된
A
는 noexcept 함수에 대한 포인터일 수 있으며, 함수 포인터 변환을 통해
A
로 변환 가능함;
d)
만약
A
가 멤버 함수 포인터인 경우, 추론된
A
는 noexcept 멤버 함수에 대한 포인터일 수 있으며, 함수 포인터 변환을 통해
A
로 변환 가능함.
|
(C++17부터) |
자세한 내용은 member template 에서 변환 함수 템플릿에 관한 다른 규칙들을 참조하십시오.
명시적 인스턴스화
템플릿 인수 추론은 명시적 인스턴스화 , 명시적 특수화 , 그리고 선언자-id가 함수 템플릿의 특수화를 참조하는 friend 선언 (예를 들어, friend ostream & operator << <> ( ... ) )에서 사용됩니다. 모든 템플릿 인수가 명시적으로 지정되거나 기본값으로 설정되지 않은 경우, 템플릿 인수 추론은 어떤 템플릿의 특수화가 참조되는지 결정하는 데 사용됩니다.
P
는 잠재적 매치로 고려 중인 함수 템플릿의 타입이며,
A
는 선언부의 함수 타입입니다. 매치가 없거나 둘 이상의 매치가 있는 경우(부분 순서 지정 후), 함수 선언은 올바르지 않은 형식입니다:
template<class X> void f(X a); // 첫 번째 템플릿 f template<class X> void f(X* a); // 두 번째 템플릿 f template<> void f<>(int* a) {} // f의 명시적 특수화 // P1 = void(X), A1 = void(int*): 추론된 X = int*, f<int*>(int*) // P2 = void(X*), A2 = void(int*): 추론된 X = int, f<int>(int*) // f<int*>(int*)와 f<int>(int*)는 부분 순서 규칙에 따라 // 더 특수화된 템플릿으로 f<int>(int*)가 선택됨
이 경우 추론에 추가 규칙이 적용됩니다: 함수 매개변수
P
i와
A
i를 비교할 때, 만약
P
i가 cv-unqualified 템플릿 매개변수에 대한 rvalue 참조("전달 참조")이고 해당하는
A
i가 lvalue 참조인 경우,
P
i는 템플릿 매개변수 타입으로 조정됩니다(T&&는 T가 됨).
할당 해제 함수 템플릿
템플릿 인수 추론은
할당 해제 함수
템플릿 특수화가 주어진 placement 형태의
operator new
와 일치하는지 판단할 때 사용됩니다.
P
는 잠재적 매치로 고려 중인 함수 템플릿의 타입이며,
A
는 고려 중인 placement operator new에 대한 매치가 될 할당 해제 함수의 함수 타입입니다. 매치가 없거나 둘 이상의 매치가 있는 경우(오버로드 해결 후), placement 할당 해제 함수는 호출되지 않습니다(메모리 누수가 발생할 수 있음):
struct X { X() { throw std::runtime_error(""); } static void* operator new(std::size_t sz, bool b) { return ::operator new(sz); } static void* operator new(std::size_t sz, double f) { return ::operator new(sz); } template<typename T> static void operator delete(void* ptr, T arg) { ::operator delete(ptr); } }; int main() { try { X* p1 = new (true) X; // X()가 예외를 던질 때 operator delete를 조회함 // P1 = void(void*, T), A1 = void(void*, bool): // 추론된 T = bool // P2 = void(void*, T), A2 = void(void*, double): // 추론된 T = double // 오버로드 해결이 operator delete<bool>을 선택함 } catch(const std::exception&) {} try { X* p1 = new (13.2) X; // 동일한 조회, operator delete<double>을 선택함 } catch(const std::exception&) {} }
별칭 템플릿
앨리어스 템플릿 은 추론되지 않습니다 , 단 클래스 템플릿 인수 추론 에서는 예외 (C++20부터) :
template<class T> struct Alloc {}; template<class T> using Vec = vector<T, Alloc<T>>; Vec<int> v; template<template<class, class> class TT> void g(TT<int, Alloc<int>>); g(v); // 성공: TT = vector로 추론됨 template<template<class> class TT> void f(TT<int>); f(v); // 오류: Vec은 별칭 템플릿이므로 TT를 "Vec"으로 추론할 수 없음
암시적 변환
타입 추론은 암시적 변환을 고려하지 않습니다(위에 나열된 타입 조정 외): 이것은 나중에 발생하는
오버로드 해결
의 역할입니다.그러나 템플릿 인자 추론에 참여하는 모든 매개변수에 대해 추론이 성공하고, 추론되지 않은 모든 템플릿 인자가 명시적으로 지정되거나 기본값으로 설정된 경우, 나머지 함수 매개변수는 해당 함수 인자와 비교됩니다. 명시적으로 지정된 템플릿 인자의 치환 전에 비의존적이었던 타입을 가진 각 나머지 매개변수
P
에 대해, 해당 인자
A
가
P
로 암시적으로 변환될 수 없다면 추론이 실패합니다.
템플릿 인수 추론에 어떤 템플릿 매개변수도 참여하지 않는 종속 타입 매개변수와, 명시적으로 지정된 템플릿 인수의 치환으로 인해 비종속적으로 된 매개변수는 오버로드 해결 중에 검사됩니다:
template<class T> struct Z { typedef typename T::x xx; }; template<class T> typename Z<T>::xx f(void*, T); // #1 template<class T> void f(int, T); // #2 struct A {} a; int main() { f(1, a); // #1의 경우, 추론으로 T = struct A가 결정되지만 나머지 인수 1은 // 해당 매개변수 void*로 암시적으로 변환될 수 없음: 추론 실패 // 반환 타입의 인스턴스화는 요청되지 않음 // #2의 경우, 추론으로 T = struct A가 결정되고 나머지 인수 1은 // 해당 매개변수 int로 암시적으로 변환될 수 있음: 추론 성공 // 함수 호출은 #2에 대한 호출로 컴파일됨 (추론 실패는 SFINAE) }
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 70 | C++98 | 배열 경계가 추론되는지 여부가 명시되지 않음 | 비추론으로 명시됨 |
| CWG 300 | C++98 |
type(*)(T)/T(*)()/T(*)(T)
형식의 함수 매개변수에 대해
추론이 발생했으나, 함수 포인터는 이 형식과 일치하지만 함수 참조는 일치하지 않음 |
이러한 형식을
type(T)/T()/T(T)
로 변경하여
참조도 포함할 수 있도록 함 |
| CWG 322 | C++98 |
참조 타입의 타입 매개변수가 추론을 위해
참조된 타입을 사용하도록 조정되지 않음 |
조정 추가됨 |
| CWG 976 | C++98 |
변환 연산자 템플릿에 대한 추론에서
const T&
반환 타입이
T
결과 타입과
절대 일치할 수 없음 |
이러한 일치를 허용하도록
규칙 조정됨 |
| CWG 1387 | C++11 | decltype-지정자의 표현식이 비추론 문맥이 아님 | 비추론 문맥으로 지정됨 |
| CWG 1391 | C++98 |
추론에 관여하지 않는 인수의 암시적 변환
효과가 명시되지 않음 |
위에서 설명한 대로 명시됨 |
| CWG 1591 | C++11 |
braced-init-list
에서 배열 경계와 요소 타입을
추론할 수 없음 |
추론 허용됨 |
| CWG 2052 | C++98 |
비클래스 비열거형 인수를 가진 연산자 추론이
하드 오류였음 |
다른 오버로드가 있는 경우
소프트 오류로 변경 |
| CWG 2091 | C++98 |
참조 상수 매개변수 추론이 인수와의 타입 불일치로
인해 작동하지 않음 |
타입 불일치 회피됨 |
| N3922 | C++11 | auto 의 직접-목록-초기화가 std::initializer_list 를 추론함 |
요소가 둘 이상인 경우 형식 오류,
단일 요소인 경우 요소 타입 추론 |
| CWG 2355 | C++17 |
함수 타입의
noexcept
지정자 내
값이 추론 가능하지 않음 |
추론 가능하게 변경됨 |