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부터) |
목차함수 호출로부터의 추론템플릿 인자 추론은 함수 매개변수 타입 타입
과 대조됩니다. 각 대조는 팩 확장의 다음 위치에 대한 템플릿 인자를 추론합니다:
template<class T>
int f(T(*p)(T));
int g(int);
int g(char);
f(g); // P = T(*)(T), A = overload set
// P = T(*)(T), A1 = int(int): deduced T = int
// P = T(*)(T), A2 = int(char): fails to deduce T
// only one overload works, deduction succeeds
오버로드된 함수 집합 과 에 대해 다음과 같은 조정이 이루어집니다:
P1) 이 참조 타입이 아닌 경우,
Aa)A 이 배열 타입이면, 은 배열-포인터 변환으로 얻은 포인터 타입으로 대체됩니다;
Ab)A 그렇지 않고 이 함수 타입이면, 은 함수-포인터 변환으로 얻은 포인터 타입으로 대체됩니다;
Ac)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
그렇지 않고 이 cv-한정된 타입이면, 최상위 cv-한정자는 추론을 위해 무시됩니다:
P2) 이 cv-한정된 타입이면, 최상위 cv-한정자는 추론을 위해 무시됩니다.
P3) 이 참조 타입이면, 참조된 타입이 추론에 사용됩니다.
P4) 이 cv-비한정 템플릿 매개변수에 대한 우측값 참조(소위 A전달 참조A)이고, 해당 함수 호출 인자가 좌측값인 경우, 추론을 위해 에 대한 좌측값 참조 타입이 대신 사용됩니다(참고: 이것은 std::forward의 동작의 기초입니다. 참고: 클래스 템플릿 인자 추론에서 클래스 템플릿의 템플릿 매개변수는 전달 참조가 절대 아닙니다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
}
(C++17부터)):
이러한 변환 후, 아래 설명된 대로 추론이 진행되며(섹션 , 즉 위에 나열된 조정 후의 과 로부터의 일반적인 추론이 실패하면, 다음 대안들이 추가로 고려됩니다:
P1)A A이 참조 타입이면, 추론된 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
(즉, 참조가 가리키는 타입)은 변환된 보다 더 cv-한정될 수 있습니다:
A2)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*)
을 통해 추론된 으로 변환될 수 있는 다른 포인터 또는 멤버 포인터 타입일 수 있습니다:
P3)P 이 클래스이고 이 단순-템플릿-id(템플릿 인자 목록이 있는 템플릿 이름) 또는 템플릿 인자 목록이 있는 splice 지정자(C++26부터)A 형식이면, 변환된 A은 추론된 P의 파생 클래스일 수 있습니다. 마찬가지로, 이 단순-템플릿-id(템플릿 인자 목록이 있는 템플릿 이름) 또는 템플릿 인자 목록이 있는 splice 지정자(C++26부터) 형식의 클래스에 대한 포인터이면, 변환된 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
}
비추론 컨텍스트
다음 경우들에서,
1)
nested-name-specifier
(범위 확인 연산자
::
의 왼쪽에 있는 모든 것)로 지정된 타입의
qualified-id
:
// 특정 인수를 추론에서 제외하기 위해 자주 사용되는 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>
4)
템플릿 매개변수를 참조하는 하위 표현식이 포함된 상수 템플릿 인수 또는 배열 경계:
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
5)
함수 매개변수의 매개변수 타입에 사용된 템플릿 매개변수로, 인수 추론이 수행되는 호출에서 사용되는 기본 인수를 가진 경우:
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)
6)
매개변수
P
의
A
가 함수 또는 오버로드 집합인 경우, 둘 이상의 함수가
P
와 일치하거나 어떤 함수도
P
와 일치하지 않거나 오버로드 집합에 하나 이상의 함수 템플릿이 포함되는 경우:
7)
매개변수
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로 추론됨
10)
배열 타입(단, 배열에 대한 참조나 배열에 대한 포인터는 제외)인
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
>
에서
타입으로부터의 추론
하나 이상의 타입 템플릿 매개변수
위의 형태에서,
만약
만약
폼은 중첩되고 재귀적으로 처리될 수 있습니다:
함수 템플릿의 상수 템플릿 매개변수가 함수 매개변수(역시 템플릿인)의 템플릿 매개변수 목록에서 사용되고, 해당 템플릿 인자가 추론되는 경우, 추론된 템플릿 인자의 타입(해당 인자를 포함하는 템플릿 매개변수 목록에 지정된 대로, 즉 참조가 보존됨)은 상수 템플릿 매개변수의 타입과 정확히 일치해야 합니다. 단, 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>) 호출 다른 컨텍스트함수 호출 및 연산자 표현식 외에도, 템플릿 인수 추론은 다음 상황에서 사용됩니다:
오버로드 해결
템플릿 인수 추론은 후보 템플릿 함수로부터 특수화를 생성할 때
오버로드 해결
과정에서 사용됩니다.
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 와 유사합니다. 오버로드 집합의 주소템플릿 인수 추론은 함수 템플릿을 포함한 오버로드 집합의 주소 를 취할 때 사용됩니다.
함수 템플릿의 함수 타입은
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를 결정함
이 경우 추론에 추가 규칙이 적용됩니다: 함수 매개변수
부분 순서템플릿 인수 추론은 오버로드된 함수 템플릿의 부분 순서 지정 동안 사용됩니다.
변환 함수 템플릿템플릿 인수 추론은 사용자 정의 변환 함수 템플릿 인수를 선택할 때 사용됩니다.
만약
a)
만약
P
가 배열 타입인 경우, 배열-포인터 변환을 통해 얻은 포인터 타입이
P
대신 사용됩니다;
b)
만약
P
가 함수 타입인 경우, 함수-포인터 변환을 통해 얻은 함수 포인터 타입이
P
대신 사용됩니다;
c)
만약
P
가 cv-한정된 경우, 최상위 cv-한정자는 무시됩니다.
만약
a)
만약
A
가 참조 타입인 경우,
A
는 추론된
A
보다 더 많은 cv 한정자를 가질 수 있습니다;
b)
만약
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*로 변환 가능합니다)
자세한 내용은 member template 에서 변환 함수 템플릿에 관한 다른 규칙들을 참조하십시오. 명시적 인스턴스화템플릿 인수 추론은 명시적 인스턴스화 , 명시적 특수화 , 그리고 선언자-id가 함수 템플릿의 특수화를 참조하는 friend 선언 (예를 들어, friend ostream & operator << <> ( ... ) )에서 사용됩니다. 모든 템플릿 인수가 명시적으로 지정되거나 기본값으로 설정되지 않은 경우, 템플릿 인수 추론은 어떤 템플릿의 특수화가 참조되는지 결정하는 데 사용됩니다.
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*)가 선택됨
이 경우 추론에 추가 규칙이 적용됩니다: 함수 매개변수
할당 해제 함수 템플릿
템플릿 인수 추론은
할당 해제 함수
템플릿 특수화가 주어진 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"으로 추론할 수 없음 암시적 변환
타입 추론은 암시적 변환을 고려하지 않습니다(위에 나열된 타입 조정 외): 이것은 나중에 발생하는
오버로드 해결
의 역할입니다.그러나 템플릿 인자 추론에 참여하는 모든 매개변수에 대해 추론이 성공하고, 추론되지 않은 모든 템플릿 인자가 명시적으로 지정되거나 기본값으로 설정된 경우, 나머지 함수 매개변수는 해당 함수 인자와 비교됩니다. 명시적으로 지정된 템플릿 인자의 치환 전에 비의존적이었던 타입을 가진 각 나머지 매개변수
템플릿 인수 추론에 어떤 템플릿 매개변수도 참여하지 않는 종속 타입 매개변수와, 명시적으로 지정된 템플릿 인수의 치환으로 인해 비종속적으로 된 매개변수는 오버로드 해결 중에 검사됩니다: 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++ 표준에 소급 적용되었습니다.
|