Function template
함수 템플릿은 함수들의 패밀리를 정의합니다.
목차 |
구문
template
<
parameter-list
>
function-declaration
|
(1) | ||||||||
template
<
parameter-list
>
requires
constraint
function-declaration
|
(2) | (C++20부터) | |||||||
| function-declaration-with-placeholders | (3) | (C++20부터) | |||||||
export
template
<
parameter-list
>
function-declaration
|
(4) | (C++11에서 제거됨) | |||||||
설명
| parameter-list | - | 비어 있지 않은 쉼표로 구분된 template parameters 목록으로, 각각은 constant parameter , type parameter , template parameter , 또는 이들 중 하나의 parameter pack 입니다 (C++11부터) . 다른 템플릿과 마찬가지로, 매개변수는 constrained 될 수 있습니다 (C++20부터) |
| function-declaration | - | function declaration . 선언된 함수 이름은 템플릿 이름이 됩니다. |
| constraint | - | 이 함수 템플릿이 수용하는 템플릿 매개변수를 제한하는 constraint expression 입니다 |
|
function-declaration-
with-placeholders |
- | 최소 하나의 매개변수 타입이 자리 표시자 auto 또는 Concept auto 를 사용하는 function declaration : 템플릿 매개변수 목록은 각 자리 표시자에 대해 하나씩 발명된 매개변수를 가집니다 (아래 약식 함수 템플릿 참조) |
|
|
(C++11까지) |
축약 함수 템플릿플레이스홀더 타입( auto 또는 Concept auto )이 함수 선언 또는 함수 템플릿 선언의 매개변수 목록에 나타날 때, 해당 선언은 함수 템플릿을 선언하며 각 플레이스홀더에 대해 하나의 템플릿 매개변수가 템플릿 매개변수 목록에 추가됩니다: void f1(auto); // same as template<class T> void f1(T) void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept void f3(C2 auto...); // same as template<C2... Ts> void f3(Ts...), if C2 is a concept void f4(const C3 auto*, C4 auto&); // same as template<C3 T, C4 U> void f4(const T*, U&); template<class T, C U> void g(T x, U y, C auto z); // same as template<class T, C U, C W> void g(T x, U y, W z); 축약 함수 템플릿은 모든 함수 템플릿과 마찬가지로 특수화할 수 있습니다. template<> void f4<int>(const int*, const double&); // specialization of f4<int, const double>
|
(C++20부터) |
함수 템플릿 시그니처
모든 함수 템플릿은 시그니처를 가집니다.
template-head 의 시그니처는 템플릿 매개변수 목록 으로, 템플릿 매개변수 이름과 기본 인수 , 그리고 requires-clause(있는 경우)를 제외한 것 (C++20부터) 입니다.
함수 템플릿의 시그니처는 이름, 매개변수-타입-목록, 반환 타입 , 후행 requires-clause (있는 경우) (C++20부터) , 그리고 template-head 의 시그니처를 포함합니다. 다음 경우들을 제외하고, 그 시그니처는 포함하는 네임스페이스도 포함합니다.
함수 템플릿이 클래스 멤버인 경우, 그 시그니처는 함수가 속한 클래스를 포함합니다(감싸는 네임스페이스 대신). 또한 그 시그니처는 후행 requires-clause(있는 경우) (C++20부터) , ref-qualifier(있는 경우), 그리고 (C++11부터) cv -qualifier(있는 경우)도 포함합니다.
|
함수 템플릿이 friend 이고 제약 조건에 외부 템플릿 매개변수가 포함된 경우, 해당 시그니처는 외부 네임스페이스 대신 외부 클래스를 포함합니다. |
(since C++20) |
함수 템플릿 인스턴스화
함수 템플릿 자체는 타입이나 함수가 아닙니다. 템플릿 정의만 포함하는 소스 파일로부터는 어떤 코드도 생성되지 않습니다. 코드가 나타나려면 템플릿이 인스턴스화되어야 합니다: 컴파일러가 실제 함수(또는 클래스 템플릿의 경우 클래스)를 생성할 수 있도록 템플릿 인자가 결정되어야 합니다.
명시적 인스턴스화
template
return-type
name
<
argument-list
>
(
parameter-list
)
;
|
(1) | ||||||||
template
return-type
name
(
parameter-list
)
;
|
(2) | ||||||||
extern
template
return-type
name
<
argument-list
>
(
parameter-list
)
;
|
(3) | (C++11부터) | |||||||
extern
template
return-type
name
(
parameter-list
)
;
|
(4) | (C++11부터) | |||||||
명시적 인스턴스 정의는 그것이 참조하는 함수나 멤버 함수의 인스턴스화를 강제합니다. 템플릿 정의 이후 프로그램 어디에서나 나타날 수 있으며, 주어진 인수 목록에 대해서는 프로그램 내에서 단 한 번만 나타날 수 있고, 진단은 요구되지 않습니다.
|
명시적 인스턴스화 선언(extern template)은 암시적 인스턴스화를 방지합니다: 암시적 인스턴스화를 유발하는 코드는 프로그램 내 다른 곳에서 제공된 명시적 인스턴스화 정의를 사용해야 합니다. |
(since C++11) |
함수 템플릿 특수화 또는 멤버 함수 템플릿 특수화의 명시적 인스턴스화에서, 후행 템플릿 인자는 함수 매개변수로부터 추론 될 수 있는 경우 생략될 수 있습니다:
template<typename T> void f(T s) { std::cout << s << '\n'; } template void f<double>(double); // f<double>(double)의 인스턴스화 template void f<>(char); // f<char>(char)의 인스턴스화, 템플릿 인자 추론됨 template void f(int); // f<int>(int)의 인스턴스화, 템플릿 인자 추론됨
함수 템플릿 또는 클래스 템플릿의 멤버 함수에 대한 명시적 인스턴스화는
inline
또는
constexpr
를 사용할 수 없습니다. 명시적 인스턴스화 선언이 암시적으로 선언된 특수 멤버 함수를 지정하는 경우, 프로그램은 ill-formed입니다.
생성자 의 명시적 인스턴스화는 템플릿 매개변수 목록(구문 (1) )을 사용할 수 없으며, 추론이 가능하기 때문에(구문 (2) ) 결코 필요하지도 않습니다.
|
예비 소멸자(prospective destructor) 의 명시적 인스턴스화는 클래스의 선택된 소멸자를 지정해야 합니다. |
(since C++20) |
명시적 인스턴스화 선언은 inline 함수, auto 선언, 참조, 그리고 클래스 템플릿 특수화의 암시적 인스턴스화를 억제하지 않습니다. (따라서 명시적 인스턴스화 선언의 대상인 인라인 함수가 ODR-사용될 때, 인라인 처리를 위해 암시적으로 인스턴스화되지만, 해당 번역 단위에서는 아웃오브라인 복사본이 생성되지 않습니다)
기본 인자 를 가진 함수 템플릿의 명시적 인스턴스화 정의는 해당 인자들의 사용이 아니며, 이를 초기화하려고 시도하지 않습니다:
char* p = 0; template<class T> T g(T x = &p) { return x; } template int g<int>(int); // &p가 int가 아님에도 OK
암시적 인스턴스화
코드가 함수 정의가 존재해야 하는 상황에서 함수를 참조하거나, 정의의 존재가 프로그램의 의미에 영향을 미치는 경우 (C++11부터) , 그리고 해당 함수가 명시적으로 인스턴스화되지 않았다면 암시적 인스턴스화가 발생합니다. 템플릿 인수 목록은 컨텍스트에서 추론될 수 있는 경우 제공되지 않아도 됩니다.
#include <iostream> template<typename T> void f(T s) { std::cout << s << '\n'; } int main() { f<double>(1); // f<double>(double)를 인스턴스화하고 호출 f<>('a'); // f<char>(char)를 인스턴스화하고 호출 f(7); // f<int>(int)를 인스턴스화하고 호출 void (*pf)(std::string) = f; // f<string>(string)를 인스턴스화 pf("∇"); // f<string>(string) 호출 }
|
함수 정의의 존재는 해당 함수가 표현식에 의해 상수 평가에 필요 한 경우 프로그램의 의미 체계에 영향을 미치는 것으로 간주됩니다. 표현식의 상수 평가가 요구되지 않거나 상수 표현식 평가가 해당 정의를 사용하지 않는 경우에도 마찬가지입니다. template<typename T> constexpr int f() { return T::value; } template<bool B, typename T> void g(decltype(B ? f<T>() : 0)); template<bool B, typename T> void g(...); template<bool B, typename T> void h(decltype(int{B ? f<T>() : 0})); template<bool B, typename T> void h(...); void x() { g<false, int>(0); // OK: B ? f<T>() : 0 is not potentially constant evaluated h<false, int>(0); // error: instantiates f<int> even though B evaluates to false // and list-initialization of int from int cannot be narrowing } |
(C++11부터) |
참고:
<>
를 완전히 생략하면
overload resolution
이 템플릿과 비템플릿 오버로드를 모두 검사할 수 있습니다.
템플릿 인수 추론
함수 템플릿을 인스턴스화하려면 모든 템플릿 인자를 알아야 하지만, 모든 템플릿 인자를 명시적으로 지정할 필요는 없습니다. 가능한 경우 컴파일러는 함수 인자로부터 누락된 템플릿 인자를 추론합니다. 이는 함수 호출이 시도될 때와 함수 템플릿의 주소를 취할 때 발생합니다.
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)를 인스턴스화합니다 }
이 메커니즘은 템플릿 연산자를 사용할 수 있게 합니다. 연산자에 대한 템플릿 인수를 지정하는 구문이 함수 호출 표현식으로 재작성하는 방법 외에는 존재하지 않기 때문입니다.
템플릿 인자 추론은 함수 템플릿 이름 검색 (여기에는 인자 의존 검색 이 포함될 수 있음) 이후, 오버로드 해결 이전에 발생합니다.
자세한 내용은 template argument deduction 를 참조하십시오.
명시적 템플릿 인수
함수 템플릿의 템플릿 인자는 다음으로부터 얻어질 수 있습니다
- 템플릿 인수 추론
- 기본 템플릿 인수
- 명시적으로 지정 (다음 컨텍스트에서 가능):
-
- 함수 호출 표현식에서
- 함수의 주소를 취할 때
- 함수에 대한 참조가 초기화될 때
- 멤버 함수 포인터가 형성될 때
- 명시적 특수화에서
- 명시적 인스턴스화에서
- friend 선언에서
오버로드된 연산자 , 변환 함수 , 그리고 생성자에 대해서는 함수 이름을 사용하지 않고 호출되기 때문에 템플릿 인자를 명시적으로 지정할 수 있는 방법이 없습니다.
지정된 템플릿 인자는 종류에 따라 템플릿 매개변수와 일치해야 합니다(즉, 타입은 타입으로, 상수는 상수로, 템플릿은 템플릿으로). 매개변수보다 많은 인자를 가질 수 없습니다 (단, 하나의 매개변수가 매개변수 팩인 경우에는 각 비-팩 매개변수에 대한 인자가 있어야 함) (C++11부터) .
지정된 상수 인수는 해당하는 상수 템플릿 매개변수의 타입과 일치하거나, 해당 타입으로 변환 가능 해야 합니다.
템플릿 인자 추론에 참여하지 않는 함수 매개변수(예: 해당 템플릿 인자가 명시적으로 지정된 경우)는 해당 함수 매개변수의 타입으로의 암시적 변환을 적용받습니다(일반적인 오버로드 해결 에서와 같이).
|
명시적으로 지정된 템플릿 매개변수 팩은 추가 인수가 있는 경우 템플릿 인수 추론에 의해 확장될 수 있습니다: template<class... Types> void f(Types... values); void g() { f<int*, float*>(0, 0, 0); // Types = {int*, float*, int} } |
(since C++11) |
템플릿 인자 치환
모든 템플릿 인자가 지정되거나, 추론되거나, 기본 템플릿 인자에서 얻어진 경우, 함수 매개변수 목록에서 템플릿 매개변수의 모든 사용은 해당 템플릿 인자로 대체됩니다.
함수 템플릿의 치환 실패(즉, 추론되거나 제공된 템플릿 인자로 템플릿 매개변수를 대체하는 데 실패하는 경우)는 해당 함수 템플릿을 오버로드 집합 에서 제거합니다. 이를 통해 템플릿 메타프로그래밍을 사용하여 오버로드 집합을 조작하는 여러 방법이 가능해집니다: 자세한 내용은 SFINAE 를 참조하십시오.
치환 후, 배열 및 함수 타입의 모든 함수 매개변수는 포인터로 조정되며 모든 최상위 cv-한정자는 함수 매개변수에서 제거됩니다(일반적인 함수 선언 에서와 같이).
최상위 cv-한정자(cv-qualifiers)의 제거는 함수 내에서 나타나는 매개변수의 타입에 영향을 주지 않습니다:
template<class T> void f(T t); template<class X> void g(const X x); template<class Z> void h(Z z, Z* zp); // 동일한 타입을 가진 두 개의 다른 함수이지만 // 함수 내에서 t는 다른 cv 한정자를 가짐 f<int>(1); // 함수 타입은 void(int), t는 int f<const int>(1); // 함수 타입은 void(int), t는 const int // 동일한 타입과 동일한 x를 가진 두 개의 다른 함수 // (이 두 함수에 대한 포인터는 동일하지 않으며, // 함수 내 정적 변수들은 다른 주소를 가짐) g<int>(1); // 함수 타입은 void(int), x는 const int g<const int>(1); // 함수 타입은 void(int), x는 const int // 최상위 수준의 cv 한정자만 제거됨: h<const int>(1, NULL); // 함수 타입은 void(int, const int*) // z는 const int, zp는 const int*
함수 템플릿 오버로딩
함수 템플릿과 비템플릿 함수는 오버로드될 수 있습니다.
비템플릿 함수는 항상 동일한 타입을 가진 템플릿 특수화와 구별됩니다. 서로 다른 함수 템플릿의 특수화들은 동일한 타입을 가지더라도 항상 서로 구별됩니다. 동일한 반환 타입과 매개변수 목록을 가진 두 함수 템플릿은 서로 구별되며, 명시적 템플릿 인자 목록으로 구분할 수 있습니다.
함수 매개변수 목록이나 반환 타입에 타입 또는 상수 템플릿 매개변수를 사용하는 표현식이 나타나면, 해당 표현식은 오버로딩을 목적으로 함수 템플릿 시그니처의 일부로 남아 있습니다:
template<int I, int J> A<I+J> f(A<I>, A<J>); // 오버로드 #1 template<int K, int L> A<K+L> f(A<K>, A<L>); // #1과 동일 template<int I, int J> A<I-J> f(A<I>, A<J>); // 오버로드 #2
템플릿 매개변수를 포함하는 두 표현식은 동등(equivalent) 하다고 불리며, 이는 이러한 표현식을 포함하는 두 함수 정의가 ODR 에 따라 동일할 경우입니다. 즉, 두 표현식은 동일한 토큰 시퀀스를 포함하며, 해당 이름들이 이름 검색을 통해 동일한 개체로 해결됩니다. 단, 템플릿 매개변수의 이름은 다를 수 있습니다. 두 개의 lambda expressions 는 절대 동등하지 않습니다. (C++20 이후)
template<int I, int J> void f(A<I+J>); // 템플릿 오버로드 #1 template<int K, int L> void f(A<K+L>); // #1과 동등함
두 dependent expressions 가 동등한지 판단할 때, 이름 조회의 결과가 아닌 관련된 dependent names만 고려됩니다. 동일한 템플릿의 여러 선언이 이름 조회 결과에서 다른 경우, 첫 번째 그러한 선언이 사용됩니다:
template<class T> decltype(g(T())) h(); // decltype(g(T()))는 종속 타입입니다 int g(int); template<class T> decltype(g(T())) h() { // h()의 재선언은 이전 조회 결과를 사용합니다 return g(T()); // 비록 이 위치의 조회에서는 g(int)를 찾을 수 있지만 } int i = h<int>(); // 템플릿 인자 치환 실패; g(int) // 는 h()의 첫 번째 선언 시점에 스코프 내에 없었습니다
두 함수 템플릿은 다음 조건을 만족할 때 동등하다(equivalent) 고 간주됩니다
- 동일한 범위에서 선언되었을 때
- 동일한 이름을 가질 때
- 동등한 템플릿 매개변수 목록을 가질 때, 즉 목록의 길이가 같고 각 대응하는 매개변수 쌍에 대해 다음 조건이 모두 참일 때:
-
- 두 매개변수가 동일한 종류인 경우(둘 다 타입, 둘 다 상수, 또는 둘 다 템플릿)
|
(since C++11) |
-
- 상수인 경우, 해당 유형이 동등합니다,
- 템플릿인 경우, 해당 템플릿 매개변수가 동등합니다,
|
(C++20부터) |
- 반환 타입과 매개변수 목록에서 템플릿 매개변수를 포함하는 표현식들은 동등합니다
|
(since C++20) |
두 개의 potentially-evaluated (since C++20) 표현식이 서로 equivalent 하지 않지만, 주어진 템플릿 인자 집합에 대해 두 표현식의 평가 결과가 동일한 값을 생성할 때, 이를 functionally equivalent 라고 합니다.
두 함수 템플릿은 반환 타입과 매개변수 목록에서 템플릿 매개변수를 포함하는 하나 이상의 표현식이 기능적으로 동등 하다는 점을 제외하고 동등 할 경우 기능적으로 동등 한 것으로 간주됩니다.
|
또한, 두 함수 템플릿의 제약 조건이 서로 다르게 지정되었지만, 동일한 템플릿 인자 목록 집합을 수용하고 만족시키는 경우, 이들은 기능적으로 동등 (functionally equivalent)하지만 동등 (equivalent)하지는 않습니다. |
(since C++20) |
만약 프로그램이 기능적으로 동등한 함수 템플릿 선언을 포함하지만 동등하지 않은 경우, 해당 프로그램은 형식이 잘못되었으며 진단 메시지는 요구되지 않습니다.
// 동등함 template<int I> void f(A<I>, A<I+10>); // 오버로드 #1 template<int I> void f(A<I>, A<I+10>); // 오버로드 #1의 재선언 // 동등하지 않음 template<int I> void f(A<I>, A<I+10>); // 오버로드 #1 template<int I> void f(A<I>, A<I+11>); // 오버로드 #2 // 기능적으로 동등하지만 동등하지 않음 // 이 프로그램은 형식 오류이며, 진단이 필요하지 않음 template<int I> void f(A<I>, A<I+10>); // 오버로드 #1 template<int I> void f(A<I>, A<I+1+2+3+4>); // 기능적으로 동등함
동일한 함수 템플릿 특수화가 둘 이상의 오버로드된 함수 템플릿과 일치할 때(이는 종종 템플릿 인수 추론 에서 발생합니다), 오버로드된 함수 템플릿의 부분 순서 지정 이 최적의 일치를 선택하기 위해 수행됩니다.
구체적으로, 부분 순서화는 다음과 같은 상황에서 발생합니다:
template<class X> void f(X a); template<class X> void f(X* a); int* p; f(p);
template<class X> void f(X a); template<class X> void f(X* a); void (*p)(int*) = &f;
|
이 섹션은 불완전합니다
이유: 미니 예시 필요 |
template<class X> void f(X a); // first template f template<class X> void f(X* a); // second template f template<> void f<>(int *a) {} // explicit specialization // template argument deduction comes up with two candidates: // f<int*>(int*) and f<int>(int*) // partial ordering selects f<int>(int*) as more specialized
비공식적으로 "A가 B보다 더 특수화되었다"는 것은 "A가 B보다 더 적은 타입을 수용한다"는 의미입니다.
공식적으로, 두 함수 템플릿 중 어느 것이 더 특수화되었는지 결정하기 위해, 부분 순서 지정 프로세스는 먼저 두 템플릿 중 하나를 다음과 같이 변환합니다:
- 각 타입, 상수, 템플릿 매개변수에 대해, 매개변수 팩을 포함하여, (C++11부터) 고유한 가상 타입, 값, 또는 템플릿이 생성되어 템플릿의 함수 타입에 대입됩니다.
-
비교 중인 두 함수 템플릿 중 하나만 멤버 함수이고, 해당 함수 템플릿이 어떤 클래스
A의 비정적 멤버인 경우, 해당 매개변수 목록에 새로운 첫 번째 매개변수가 삽입됩니다. 함수 템플릿의 cv-한정자를 cv 로, 함수 템플릿의 ref-한정자를 ref 로 주어졌을 때, (C++11부터) 새로운 매개변수 타입은 cvA&입니다. 단, ref 가&&이거나, ref 가 존재하지 않고 다른 템플릿의 첫 번째 매개변수가 rvalue 참조 타입인 경우에는 타입이 cvA&&가 됩니다. (C++11부터) 이는 멤버 함수와 비멤버 함수 모두로 조회되는 연산자들의 순서 결정에 도움을 줍니다:
struct A {}; template<class T> struct B { template<class R> int operator*(R&); // #1 }; template<class T, class R> int operator*(T&, R&); // #2 int main() { A a; B<A> b; b * a; // int B<A>::operator*(R&)에 대한 템플릿 인자 추론은 R=A를 제공함 // int operator*(T&, R&)에 대해 T=B<A>, R=A // 부분 순서화를 위해, 멤버 템플릿 B<A>::operator*는 // template<class R> int operator*(B<A>&, R&);로 변환됨 // 다음 사이의 부분 순서화: // int operator*( T&, R&) T=B<A>, R=A // 와 int operator*(B<A>&, R&) R=A // int operator*(B<A>&, A&)를 더 특수화된 것으로 선택함 }
두 템플릿 중 하나가 위에서 설명한 대로 변환된 후, template argument deduction 이 변환된 템플릿을 인수 템플릿으로, 다른 템플릿의 원본 템플릿 타입을 매개변수 템플릿으로 사용하여 실행됩니다. 그런 다음 두 번째 템플릿(변환 후)을 인수로, 원본 형태의 첫 번째 템플릿을 매개변수로 사용하여 이 과정이 반복됩니다.
순서를 결정하는 데 사용되는 유형은 컨텍스트에 따라 다릅니다:
- 함수 호출의 맥락에서, 타입들은 함수 호출에 인수가 있는 함수 매개변수 타입들입니다 (기본 함수 인수, parameter packs, (C++11부터) 그리고 생략 부호 매개변수는 고려되지 않음 -- 아래 예제 참조)
- 사용자 정의 변환 함수 호출의 맥락에서, 변환 함수 템플릿들의 반환 타입들이 사용됩니다
- 다른 맥락에서는, 함수 템플릿 타입이 사용됩니다
위 목록의 각 타입은 매개변수 템플릿으로부터 추론됩니다. 추론이 시작되기 전에, 매개변수 템플릿의 각 매개변수
P
와 인자 템플릿의 해당 인자
A
는 다음과 같이 조정됩니다:
-
만약
P와A가 모두 이전에 참조 타입이었다면, 어느 쪽이 더 많은 cv-qualification을 가지는지 결정합니다 (다른 모든 경우에는 부분 순서 결정 목적으로 cv-qualification은 무시됩니다) -
만약
P가 참조 타입이라면, 그것이 참조하는 타입으로 대체됩니다 -
만약
A가 참조 타입이라면, 그것이 참조하는 타입으로 대체됩니다 -
만약
P가 cv-qualified라면,P는 자신의 cv-unqualified 버전으로 대체됩니다 -
만약
A가 cv-qualified라면,A는 자신의 cv-unqualified 버전으로 대체됩니다
이러한 조정 후,
A
에서
P
를 추론하는 작업은
타입으로부터의 템플릿 인수 추론
에 따라 수행됩니다.
|
만약
만약
|
(C++11부터) |
변환된 템플릿-1의 인수
A
가 템플릿-2의 해당 매개변수
P
를 추론하는 데 사용될 수 있지만 그 반대는 불가능한 경우, 이
A
는 이
P/A
쌍에 의해 추론되는 타입(들)과 관련하여
P
보다 더 특수화된 것입니다.
양방향에서 추론이 성공하고, 원본
P
와
A
가 참조 타입이었다면, 추가 테스트가 수행됩니다:
-
만약
A가 lvalue reference이고P가 rvalue reference라면,A가P보다 더 특수화된 것으로 간주됩니다. -
만약
A가P보다 더 많은 cv-qualified라면,A가P보다 더 특수화된 것으로 간주됩니다.
다른 모든 경우에는, 이
P/A
쌍에 의해 추론된 타입과 관련하여 어느 템플릿도 다른 것보다 더 특수화되지 않습니다.
모든 방향에서 각
P
와
A
를 고려한 후, 고려된 각 타입에 대해 다음이 성립하는 경우,
- template-1은 모든 타입에 대해 template-2 이상으로 특수화됨
- template-1은 일부 타입에 대해 template-2보다 더 특수화됨
- template-2는 어떤 타입에 대해서도 template-1보다 더 특수화되지 않거나 어떤 타입에 대해서도 적어도 동등하게 특수화되지 않음
그러면 template-1이 template-2보다 더 특수화된 것입니다. 템플릿 순서를 바꾼 후 위 조건들이 참이면, template-2가 template-1보다 더 특수화된 것입니다. 그렇지 않으면, 어느 템플릿도 다른 것보다 더 특수화되지 않습니다.
|
동점 상황에서, 한 함수 템플릿이 후행 매개변수 팩을 가지고 있고 다른 함수 템플릿은 그렇지 않은 경우, 생략된 매개변수를 가진 함수 템플릿이 빈 매개변수 팩을 가진 함수 템플릿보다 더 특수화된 것으로 간주됩니다. |
(since C++11) |
모든 오버로드된 템플릿 쌍을 고려한 후, 다른 모든 것보다 명확하게 더 특수화된 것이 하나 있다면 해당 템플릿의 특수화가 선택되고, 그렇지 않으면 컴파일이 실패합니다.
다음 예제에서 가상 인수는 U1, U2로 호출됩니다:
template<class T> void f(T); // 템플릿 #1 template<class T> void f(T*); // 템플릿 #2 template<class T> void f(const T*); // 템플릿 #3 void m() { const int* p; f(p); // 오버로드 해결 선택: #1: void f(T ) [T = const int *] // #2: void f(T*) [T = const int] // #3: void f(const T *) [T = int] // 부분 순서화: // #1에서 변환된 #2: void(T)에서 void(U1*): P=T A=U1*: 추론 성공: T=U1* // #2에서 변환된 #1: void(T*)에서 void(U1): P=T* A=U1: 추론 실패 // #2가 #1보다 T에 대해 더 특수화됨 // #1에서 변환된 #3: void(T)에서 void(const U1*): P=T, A=const U1*: 성공 // #3에서 변환된 #1: void(const T*)에서 void(U1): P=const T*, A=U1: 실패 // #3이 #1보다 T에 대해 더 특수화됨 // #2에서 변환된 #3: void(T*)에서 void(const U1*): P=T* A=const U1*: 성공 // #3에서 변환된 #2: void(const T*)에서 void(U1*): P=const T* A=U1*: 실패 // #3이 #2보다 T에 대해 더 특수화됨 // 결과: #3이 선택됨 // 즉, f(const T*)는 f(T)나 f(T*)보다 더 특수화됨 }
template<class T> void f(T, T*); // #1 template<class T> void f(T, int*); // #2 void m(int* p) { f(0, p); // #1에 대한 추론: void f(T, T*) [T = int] // #2에 대한 추론: void f(T, int*) [T = int] // 부분 순서화: // #1에서 #2: void(T,T*)에서 void(U1,int*): P1=T, A1=U1: T=U1 // P2=T*, A2=int*: T=int: 실패 // #2에서 #1: void(T,int*)에서 void(U1,U2*): P1=T A1=U1: T=U1 // P2=int* A2=U2*: 실패 // T에 대해 어느 것도 더 특수화되지 않았으므로 호출이 모호함 }
template<class T> void g(T); // 템플릿 #1 template<class T> void g(T&); // 템플릿 #2 void m() { float x; g(x); // #1로부터 추론: void g(T ) [T = float] // #2로부터 추론: void g(T&) [T = float] // 부분 순서화: // #2에서 #1: void(T) from void(U1&): P=T, A=U1 (조정 후), ok // #1에서 #2: void(T&) from void(U1): P=T (조정 후), A=U1: ok // T에 대해 어느 것도 더 특수화되지 않았으므로 호출은 모호함 }
template<class T> struct A { A(); }; template<class T> void h(const T&); // #1 template<class T> void h(A<T>&); // #2 void m() { A<int> z; h(z); // #1에서의 추론: void h(const T &) [T = A<int>] // #2에서의 추론: void h(A<T> &) [T = int] // 부분 순서화: // #1에서 #2: void(const T&)에서 void(A<U1>&): P=T A=A<U1>: ok T=A<U1> // #2에서 #1: void(A<T>&)에서 void(const U1&): P=A<T> A=const U1: 실패 // #2가 #1보다 T에 대해 더 특수화됨 const A<int> z2; h(z2); // #1에서의 추론: void h(const T&) [T = A<int>] // #2에서의 추론: void h(A<T>&) [T = int], 그러나 치환 실패 // 선택할 수 있는 오버로드가 하나뿐이며, 부분 순서화는 시도되지 않음, #1이 호출됨 }
호출 컨텍스트는 명시적인 호출 인수가 있는 매개변수만 고려하므로, 함수 매개변수 팩, (C++11 이후) 생략 부호 매개변수, 그리고 명시적인 호출 인수가 없는 기본 인수를 가진 매개변수는 무시됩니다:
template<class T> void f(T); // #1 template<class T> void f(T*, int = 1); // #2 void m(int* ip) { int* ip; f(ip); // #2를 호출합니다 (T*가 T보다 더 특수화되었습니다) }
template<class T> void g(T); // #1 template<class T> void g(T*, ...); // #2 void m(int* ip) { g(ip); // #2를 호출합니다 (T*가 T보다 더 특수화되었습니다) }
template<class T, class U> struct A {}; template<class T, class U> void f(U, A<U, T>* p = 0); // #1 template<class U> void f(U, A<U, U>* p = 0); // #2 void h() { f<int>(42, (A<int, int>*)0); // #2 호출 f<int>(42); // 오류: 모호함 }
template<class T> void g(T, T = T()); // #1 template<class T, class... U> void g(T, U...); // #2 void h() { g(42); // 오류: 모호함 }
template<class T, class... U> void f(T, U...); // #1 template<class T> void f(T); // #2 void h(int i) { f(&i); // 매개변수 팩과 매개변수 없음 사이의 우선순위 규칙에 의해 #2를 호출함 // (참고: DR692와 DR1395 사이에서는 모호했음) }
template<class T, class... U> void g(T*, U...); // #1 template<class T> void g(T); // #2 void h(int i) { g(&i); // OK: #1을 호출함 (T*가 T보다 더 특수화됨) }
template<class... T> int f(T*...); // #1 template<class T> int f(const T&); // #2 f((int*)0); // OK: #2를 선택함; 가변 템플릿보다 비가변 템플릿이 더 특수화됨 // (DR1395 이전에는 양방향에서 추론이 실패하여 모호했음)
template<class... Args> void f(Args... args); // #1 template<class T1, class... Args> void f(T1 a1, Args... args); // #2 template<class T1, class T2> void f(T1 a1, T2 a2); // #3 f(); // #1 호출 f(1, 2, 3); // #2 호출 f(1, 2); // #3 호출; 비가변 템플릿 #3이 가변 템플릿 #1과 #2보다 // 더 특수화되어 있음
부분 순서 결정 과정에서의 템플릿 인수 추론 중에는, 부분 순서를 고려하는 타입들 중 어느 것에도 인수가 사용되지 않는 경우 템플릿 매개변수가 인수와 일치할 필요가 없습니다
template<class T> T f(int); // #1 template<class T, class U> T f(U); // #2 void g() { f<int>(1); // #1의 특수화는 명시적: T f(int) [T = int] // #2의 특수화는 추론됨: T f(U) [T = int, U = int] // 부분 순서화 (인자 타입만 고려): // #1에서 #2: T(int)에서 U1(U2): 실패 // #2에서 #1: T(U)에서 U1(int): 성공: U=int, T 미사용 // #1 호출 }
|
템플릿 매개변수 팩을 포함하는 함수 템플릿의 부분 순서는 해당 템플릿 매개변수 팩에 대해 추론된 인수의 개수와 무관합니다. template<class...> struct Tuple {}; template<class... Types> void g(Tuple<Types...>); // #1 template<class T1, class... Types> void g(Tuple<T1, Types...>); // #2 template<class T1, class... Types> void g(Tuple<T1, Types&...>); // #3 g(Tuple<>()); // calls #1 g(Tuple<int, float>()); // calls #2 g(Tuple<int, float&>()); // calls #3 g(Tuple<int>()); // calls #3 |
(C++11부터) |
|
이 섹션은 불완전합니다
이유: 14.8.3[temp.over] |
함수 템플릿 호출을 컴파일할 때, 컴파일러는 비템플릿 오버로드, 템플릿 오버로드, 그리고 템플릿 오버로드의 특수화 사이에서 결정해야 합니다.
template<class T> void f(T); // #1: 템플릿 오버로드 template<class T> void f(T*); // #2: 템플릿 오버로드 void f(double); // #3: 비템플릿 오버로드 template<> void f(int); // #4: #1의 특수화 f('a'); // #1 호출 f(new int(1)); // #2 호출 f(1.0); // #3 호출 f(1); // #4 호출
함수 오버로드 vs 함수 특수화
템플릿이 아닌 오버로드와 기본 템플릿 오버로드만 오버로드 해결에 참여한다는 점에 유의하십시오. 특수화는 오버로드가 아니며 고려되지 않습니다. 오버로드 해결이 가장 잘 일치하는 기본 함수 템플릿을 선택한 후에만, 해당 특수화들 중 더 나은 일치가 있는지 검토됩니다.
template<class T> void f(T); // #1: 모든 타입에 대한 오버로드 template<> void f(int*); // #2: int 포인터에 대한 #1의 특수화 template<class T> void f(T*); // #3: 모든 포인터 타입에 대한 오버로드 f(new int(1)); // #1의 특수화가 완벽하게 일치함에도 #3을 호출함
번역 단위의 헤더 파일을 순서화할 때 이 규칙을 기억하는 것이 중요합니다. 함수 오버로드와 함수 특수화 간의 상호작용에 대한 추가 예제를 보려면 아래를 펼쳐보세요:
| 예제 |
|---|
|
먼저 인수 종속 조회가 사용되지 않는 몇 가지 시나리오를 살펴보겠습니다. 이를 위해 우리는 ( f ) ( t ) 호출을 사용합니다. ADL 에서 설명된 바와 같이, 함수 이름을 괄호로 감싸는 것은 인수 종속 조회를 억제합니다.
이 코드 실행
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // f() POR 이전의 오버로드 #1 template<class T> void f(T*) { std::cout << "#2\n"; } // f() POR 이전의 오버로드 #2 template<class T> void g(T* t) { (f)(t); // f() POR } int main() { A* p = nullptr; g(p); // g()와 f()의 POR } // #1과 #2 모두 후보 목록에 추가됨; // #2가 더 나은 매치이므로 선택됨. 출력: #2
이 코드 실행
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { (f)(t); // f() POR } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 int main() { A* p = nullptr; g(p); // POR of g() and f() } // Only #1 is added to the candidate list; #2 is defined after POR; // therefore, it is not considered for overloading even if it is a better match. 출력: #1
이 코드 실행
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { (f)(t); // f() POR } template<> void f<>(A*) { std::cout << "#3\n"; } // #3 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 is added to the candidate list; #3 is a better match defined after POR. The // candidate list consists of #1 which is eventually selected. After that, the explicit // specialization #3 of #1 declared after POI is selected because it is a better match. // This behavior is governed by 14.7.3/6 [temp.expl.spec] and has nothing to do with ADL. 출력: #3
이 코드 실행
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { (f)(t); // f() POR } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 template<> void f<>(A*) { std::cout << "#3\n"; } // #3 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 is the only member of the candidate list and it is eventually selected. // After that, the explicit specialization #3 is skipped because it actually // specializes #2 declared after POR. 출력: #1
이 코드 실행
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { f(t); // f() POR } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 is added to the candidate list as a result of the ordinary lookup; // #2 is defined after POR but it is added to the candidate list via ADL lookup. // #2 is selected being the better match. 출력: #2
이 코드 실행
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { f(t); // f() POR } template<> void f<>(A*) { std::cout << "#3\n"; } // #3 template<class T> void f(T*) { std::cout << "#2\n"; } // #2 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 is added to the candidate list as a result of the ordinary lookup; // #2 is defined after POR but it is added to the candidate list via ADL lookup. // #2 is selected among the primary templates, being the better match. // Since #3 is declared before #2, it is an explicit specialization of #1. // Hence the final selection is #2. 출력: #2
이 코드 실행
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { f(t); // f() POR } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 template<> void f<>(A*) { std::cout << "#3\n"; } // #3 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 is added to the candidate list as a result of the ordinary lookup; // #2 is defined after POR but it is added to the candidate list via ADL lookup. // #2 is selected among the primary templates, being the better match. // Since #3 is declared after #2, it is an explicit specialization of #2; // therefore, selected as the function to call. 출력: #3
|
오버로드 해결에 대한 자세한 규칙은 overload resolution 을 참조하십시오.
함수 템플릿 특수화
|
이 섹션은 불완전합니다
이유: 14.8[temp.fct.spec] (14.8.1[temp.arg.explicit]은 이미 전체 특수화 문서에 있음: 함수 관련 내용은 여기에 포함 - 부분 특수화의 부재, 함수 오버로드와의 상호작용, 또는 해당 내용 참조) |
키워드
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 214 | C++98 | 부분 순서화의 정확한 절차가 명시되지 않음 | 명세 추가됨 |
| CWG 532 | C++98 |
비정적 멤버 함수 템플릿과 비멤버 함수 템플릿 간의
순서가 명시되지 않음 |
명세 추가됨 |
| CWG 581 | C++98 |
생성자 템플릿의 명시적 특수화나 인스턴스화에서
템플릿 인자 목록이 허용됨 |
금지됨 |
| CWG 1321 | C++98 |
첫 선언과 재선언에서 동일한 종속 이름이
동등한지 여부가 불분명했음 |
동등하며
의미는 첫 선언에서와 동일함 |
| CWG 1395 | C++11 |
A가 팩에서 왔을 때 추론이 실패했으며,
빈 팩 타이브레이커가 없었음 |
추론 허용,
타이브레이커 추가됨 |
| CWG 1406 | C++11 |
비정적 멤버 함수 템플릿에 추가된 새로운 첫 번째 매개변수의
타입이 해당 템플릿의 ref-qualifier와 관련이 없었음 |
ref-qualifier가
&&
인 경우
타입은 rvalue 참조 타입임 |
| CWG 1446 | C++11 |
ref-qualifier가 없는 비정적 멤버 함수 템플릿에 추가된
새로운 첫 번째 매개변수의 타입이 lvalue 참조 타입이었으며, 해당 멤버 함수 템플릿이 첫 번째 매개변수가 rvalue 참조 타입인 함수 템플릿과 비교되는 경우에도 마찬가지였음 |
이 경우 타입은
rvalue 참조 타입임 |
| CWG 2373 | C++98 |
부분 순서화에서 정적 멤버 함수 템플릿의 매개변수 목록에
새로운 첫 번째 매개변수가 추가됨 |
추가되지 않음 |