템플릿 매개변수와 템플릿 인자
템플릿 매개변수와 템플릿 인자 (Template parameters and template arguments)
템플릿을 "타입 가족을 찍어내는 틀"이라고 한다면, 매개변수는 그 틀의 구멍이에요. 이 페이지에서는 그 구멍에 무엇이 들어갈 수 있는지를, 즉 템플릿 매개변수의 종류와 그에 대응하는 템플릿 인자의 규칙을 정확히 다뤄요.
출처: cppreference
본문
템플릿 매개변수 (Template parameters)
모든 템플릿은 하나 이상의 템플릿 매개변수로 매개변수화돼요. 템플릿 선언 문법의 parameter-list에 표시되죠.
| 표기 | 의미 |
|---|---|
template < parameter-list > declaration |
(1) |
template < parameter-list > requires constraint declaration |
(2) (C++20부터) |
parameter-list의 각 매개변수는 다음 중 하나일 수 있어요.
- 비타입(non-type) 템플릿 매개변수
- 타입 템플릿 매개변수
- 템플릿 템플릿(template template) 매개변수
참고: P2841(Concept and variable-template template-parameters)이 의미를 바꾸지 않고 "비타입 템플릿 매개변수/인자"를 "상수 템플릿 매개변수/인자"로 개명했어요. "비타입 템플릿 매개변수"와 그 약어 "NTTP"의 광범위한 사용 때문에, 현재 용어가 유지돼요.
비타입 템플릿 매개변수 (Non-type template parameter)
| 표기 | 의미 |
|---|---|
| type name(선택) | (1) |
type name(선택) = default |
(2) |
type ... name(선택) |
(3) (C++11부터) |
- 비타입 템플릿 매개변수.
- 기본 템플릿 인자를 가진 비타입 템플릿 매개변수.
- 비타입 템플릿 매개변수 팩.
- name — 비타입 템플릿 매개변수의 이름
- default — 기본 템플릿 인자
- (C++17부터) placeholder 타입을 포함하는 타입
- (C++20부터) 추론된 클래스 타입을 위한 placeholder
**구조적 타입(structural type)**은 다음 타입 중 하나예요(선택적으로 cv 한정될 수 있으며, 그 한정자는 무시돼요).
- (객체나 함수에 대한) lvalue 참조 타입
- 정수 타입
- (객체나 함수에 대한) 포인터 타입
- (멤버 객체나 멤버 함수에 대한) 멤버 포인터 타입
- 열거형 타입
std::nullptr_t(C++11부터)- 부동소수점 타입
- 캡처가 없는 람다 표현식의 람다 클로저 타입
- 다음 성질을 가진 비클로저 리터럴 클래스 타입:
- 모든 기저 클래스와 비정적 데이터 멤버가 public이고 non-mutable이고
- 모든 기저 클래스와 비정적 데이터 멤버의 타입이 구조적 타입이거나 그것들의 (아마 다차원) 배열이다. (C++20부터)
std::meta::info(C++26부터)
배열 타입과 함수 타입은 템플릿 선언에 써도 되지만, 적절하게 객체 포인터와 함수 포인터로 자동 대체돼요.
비타입 템플릿 매개변수의 이름이 클래스 템플릿 본문 안의 표현식에서 쓰일 때, 그 타입이 lvalue 참조 타입이거나 클래스 타입(C++20부터)이 아니라면, 그것은 수정 불가능한 prvalue예요.
class Foo 형태의 템플릿 매개변수는 타입 Foo의 이름 없는 비타입 템플릿 매개변수가 아니에요. 비록 그 밖의 문맥에서 class Foo가 상세 타입 지정자(elaborated type specifier)이고 class Foo x;가 x를 타입 Foo로 선언한다 해도요.
(C++26부터) 템플릿 매개변수 객체는 std::meta::reflect_constant_array 같은 반영(reflection) 함수로도 만들 수 있어요. (C++20부터)
타입 템플릿 매개변수 (Type template parameter)
| 표기 | 의미 |
|---|---|
| type-parameter-key name(선택) | (1) |
type-parameter-key name(선택) = default |
(2) |
type-parameter-key ... name(선택) |
(3) (C++11부터) |
| type-constraint name(선택) | (4) (C++20부터) |
type-constraint name(선택) = default |
(5) (C++20부터) |
type-constraint ... name(선택) |
(6) (C++20부터) |
- type-parameter-key —
typename또는class. 타입 템플릿 매개변수 선언에서 이 두 키워드 사이에는 차이가 없어요. - type-constraint — 컨셉의 이름이거나, 컨셉의 이름 뒤에 (꺾쇠 안의) 템플릿 인자 목록이 이어지는 것. 어느 쪽이든 컨셉 이름은 선택적으로 한정될 수 있어요.
- name — 타입 템플릿 매개변수의 이름
- default — 기본 템플릿 인자
- 기본 없는 타입 템플릿 매개변수.
template<class T>
class My_vector { /* ... */ };
- 기본이 있는 타입 템플릿 매개변수.
template<class T = void>
struct My_op_functor { /* ... */ };
- 타입 템플릿 매개변수 팩.
template<typename... Ts>
class My_tuple { /* ... */ };
- 기본 없는 제약된(constrained) 타입 템플릿 매개변수.
template<My_concept T>
class My_constrained_vector { /* ... */ };
- 기본이 있는 제약된 타입 템플릿 매개변수.
template<My_concept T = void>
class My_constrained_op_functor { /* ... */ };
- 제약된 타입 템플릿 매개변수 팩.
template<My_concept... Ts>
class My_constrained_tuple { /* ... */ };
매개변수의 이름은 선택적이에요.
// 위에 보인 템플릿들의 선언:
template<class>
class My_vector;
template<class = void>
struct My_op_functor;
template<typename...>
class My_tuple;
템플릿 선언의 본문에서 타입 매개변수의 이름은, 템플릿이 인스턴스화될 때 제공되는 타입을 가리키는 typedef-name이에요.
(C++20부터) Q가 컨셉 C를 지정하는 type-constraint인 각 제약된 매개변수 P는 다음 규칙에 따라 제약 표현식 E를 도입해요.
Q가 C(인자 목록 없이)라면,- P가 매개변수 팩이 아니면, E는 단순히
C<P>예요. - 그렇지 않으면(P가 매개변수 팩), E는 폴드 표현식
(C<P> && ...)이에요.
- P가 매개변수 팩이 아니면, E는 단순히
Q가C<A1,A2,...,AN>이라면, E는 각각C<P,A1,A2,...AN>또는(C<P,A1,A2,...AN> && ...)이에요.
template<typename T>
concept C1 = true;
template<typename... Ts> // 가변 인자 컨셉
concept C2 = true;
template<typename T, typename U>
concept C3 = true;
template<C1 T> struct s1; // 제약 표현식은 C1<T>
template<C1... T> struct s2; // 제약 표현식은 (C1<T> && ...)
template<C2... T> struct s3; // 제약 표현식은 (C2<T> && ...)
template<C3<int> T> struct s4; // 제약 표현식은 C3<T, int>
template<C3<int>... T> struct s5; // 제약 표현식은 (C3<T, int> && ...)
템플릿 템플릿 매개변수 (Template template parameter)
| 표기 | 의미 |
|---|---|
template < parameter-list > type-parameter-key name(선택) |
(1) |
template < parameter-list > type-parameter-key name(선택) = default |
(2) |
template < parameter-list > type-parameter-key ... name(선택) |
(3) (C++11부터) |
- type-parameter-key —
class또는(C++17부터)typename
- 선택적 이름을 가진 템플릿 템플릿 매개변수.
- 선택적 이름과 기본을 가진 템플릿 템플릿 매개변수.
- 선택적 이름을 가진 템플릿 템플릿 매개변수 팩.
템플릿 선언의 본문에서 이 매개변수의 이름은 template-name이에요(인스턴스화하려면 인자가 필요해요).
template<typename T>
class my_array {};
// 두 개의 타입 템플릿 매개변수와 하나의 템플릿 템플릿 매개변수:
template<typename K, typename V, template<typename> typename C = my_array>
class Map
{
C<K> key;
C<V> value;
};
템플릿 매개변수의 이름 결정 (Name resolution for template parameters)
템플릿 매개변수의 이름은 그 스코프(중첩 스코프 포함) 안에서 재선언될 수 없어요. 템플릿 매개변수는 템플릿 이름과 같은 이름을 가질 수 없어요.
template<class T, int N>
class Y
{
int T; // error: 템플릿 매개변수 재선언
void f()
{
char T; // error: 템플릿 매개변수 재선언
}
};
template<class X>
class X; // error: 템플릿 매개변수 재선언
클래스 템플릿 정의 밖에 나타나는 클래스 템플릿 멤버의 정의에서, 클래스 템플릿 멤버의 이름은 둘러싸는 클래스 템플릿들의 템플릿 매개변수 이름을 가리지만, 멤버가 클래스나 함수 템플릿이라면 그 멤버의 템플릿 매개변수는 가리지 않아요.
template<class T>
struct A
{
struct B {};
typedef void C;
void f();
template<class U>
void g(U);
};
template<class B>
void A<B>::f()
{
B b; // A의 B, 템플릿 매개변수가 아님
}
template<class B>
template<class C>
void A<B>::g(C)
{
B b; // A의 B, 템플릿 매개변수가 아님
C c; // 템플릿 매개변수 C, A의 C가 아님
}
클래스 템플릿 정의를 포함하는 이름공간 밖에 나타나는 클래스 템플릿 멤버의 정의에서, 템플릿 매개변수의 이름은 이 이름공간의 멤버 이름을 가려요.
namespace N
{
class C {};
template<class T>
class B
{
void f(T);
};
}
template<class C>
void N::B<C>::f(C)
{
C b; // C는 템플릿 매개변수, N::C가 아님
}
클래스 템플릿 정의나 템플릿 정의 밖에 나타나는 그런 템플릿 멤버의 정의에서, 각 비의존 기저 클래스에 대해, 기저 클래스의 이름이나 기저 클래스 멤버의 이름이 템플릿 매개변수의 이름과 같다면, 기저 클래스 이름이나 멤버 이름이 템플릿 매개변수 이름을 가려요.
struct A
{
struct B {};
int C;
int Y;
};
template<class B, class C>
struct X : A
{
B b; // A의 B
C b; // error: A의 C는 타입 이름이 아님
};
템플릿 인자 (Template arguments)
템플릿이 인스턴스화되려면, 모든 템플릿 매개변수(타입, 비타입, 템플릿)가 대응하는 템플릿 인자로 대체되어야 해요. 클래스 템플릿의 경우 인자는 명시적으로 제공되거나, 초기화로부터 추론되거나(C++17부터), 기본값이 돼요. 함수 템플릿의 경우 인자는 명시적으로 제공되거나, 문맥에서 추론되거나, 기본값이 돼요.
인자가 type-id와 표현식 둘 다로 해석될 수 있다면, 대응하는 템플릿 매개변수가 비타입이어도 항상 type-id로 해석돼요.
template<class T>
void f(); // #1
template<int I>
void f(); // #2
void g()
{
f<int()>(); // "int()"는 타입이자 표현식이고,
// 타입으로 해석되므로 #1을 호출
}
비타입 템플릿 인자 (Non-type template arguments)
(C++11까지) 비타입 템플릿 매개변수와 함께 쓸 수 있는 템플릿 인자는 어떤 manifestly constant-evaluated 표현식이든 될 수 있어요.
(C++11부터) 비타입 템플릿 매개변수와 함께 쓸 수 있는 템플릿 인자는 어떤 초기화 절(initializer clause)이든 될 수 있어요. 초기화 절이 표현식이라면, 그것은 manifestly constant-evaluated여야 해요.
비타입 템플릿 매개변수 선언의 타입을 T라고 하고, 그 매개변수에 제공된 템플릿 인자를 E라고 하자.
(C++26부터) 발명된 선언(invented declaration) T x = E;이 정적 저장 기간을 가진 constexpr 변수의 정의에 대한 의미 제약을 충족해야 해요.
(C++17부터) T가 placeholder 타입을 포함하거나 추론된 클래스 타입을 위한 placeholder라면, 템플릿 매개변수의 타입은 발명된 선언 T x = E;의 변수 x에 대해 추론된 타입이에요.
- 추론된 매개변수 타입이 구조적 타입이 아니면 프로그램은 ill-formed예요.
- placeholder 타입을 쓰는 비타입 템플릿 매개변수 팩의 경우, 타입은 각 템플릿 인자에 대해 독립적으로 추론되고 일치할 필요가 없어요.
template<auto n>
struct B { /* ... */ };
B<5> b1; // OK: 비타입 템플릿 매개변수 타입은 int
B<'a'> b2; // OK: 비타입 템플릿 매개변수 타입은 char
B<2.5> b3; // error (C++20까지): 비타입 템플릿 매개변수 타입은 double일 수 없음
// C++20 추론된 클래스 타입 placeholder, 클래스 템플릿 인자는 호출 지점에서 추론됨
template<std::array arr>
void f();
f<std::array<double, 8>{}>();
template<auto...>
struct C {};
C<'C', 0, 2L, nullptr> x; // OK
(아마 추론된, C++17부터) 타입 T의 비타입 템플릿 매개변수 P의 값은 그 템플릿 인자 A로부터 다음과 같이 결정돼요.
(C++11까지) A가 타입 T의 변환된 상수 표현식이면 P의 값은 A(변환된 채로)예요. 그렇지 않으면 프로그램은 ill-formed예요.
(C++11~C++20) A가 표현식이라면:
A가 타입T의 변환된 상수 표현식이면P의 값은A(변환된 채로)예요.- 그렇지 않으면 프로그램은 ill-formed예요.
그렇지 않으면(A가 중괄호로 둘러싸인 초기화 목록), 임시 변수 constexpr T v = A;가 도입돼요. P의 값은 v의 값이에요. v의 수명은 초기화 직후 끝나요.
(C++20부터) T가 클래스 타입이 아니고 A가 표현식이라면:
A가 타입T의 변환된 상수 표현식이면P의 값은A(변환된 채로)예요.- 그렇지 않으면 프로그램은 ill-formed예요.
그렇지 않으면(T가 클래스 타입이거나 A가 중괄호로 둘러싸인 초기화 목록), 임시 변수 constexpr T v = A;가 도입돼요.
T가 클래스 타입이면 **템플릿 매개변수 객체(template parameter object)**가 존재해요(P로도 표시됨).P는v와 template-argument-equivalent인 불특정 후보 초기화로부터 복사 초기화돼요.v의 수명은v와P의 초기화 직후 끝나요.P의 초기화가 다음 조건 중 하나를 충족하면 프로그램은 ill-formed예요.- 초기화가 ill-formed일 것.
- 초기화를 위한 발명된 declarator-initializer 시퀀스의 전체 표현식이, manifestly constant-evaluated 표현식으로 해석될 때 상수 표현식이 아닐 것.
- 초기화가
P가v와 template-argument-equivalent가 되지 않게 할 것.
- 그렇지 않으면
P의 값은v의 값이에요.
template<int i>
struct C { /* ... */ };
C<{42}> c1; // OK
template<auto n>
struct B { /* ... */ };
struct J1
{
J1* self = this;
};
B<J1{}> j1; // error: 템플릿 매개변수 객체의 초기화가
// 상수 표현식이 아님
struct J2
{
J2 *self = this;
constexpr J2() {}
constexpr J2(const J2&) {}
};
B<J2{}> j2; // error: 템플릿 매개변수 객체가 도입된 임시 변수와
// template-argument-equivalent가 아님
(C++17까지) 비타입 템플릿 매개변수를 가진 템플릿을 인스턴스화할 때 다음 제한이 적용됐어요.
- 정수 및 산술 타입에 대해, 인스턴스화 중에 제공된 템플릿 인자는 템플릿 매개변수 타입의 변환된 상수 표현식이어야 해요(그래서 특정 암시적 변환이 적용돼요).
- 객체 포인터에 대해, 템플릿 인자는 정적 저장 기간과 연결(internal 또는 external)을 가진 완전한 객체의 주소를 지정하거나, 적절한 널 포인터나
std::nullptr_t(C++11부터) 값으로 평가되는 상수 표현식이어야 해요. - 함수 포인터에 대해, 유효한 인자는 연결을 가진 함수에 대한 포인터(또는 널 포인터 값으로 평가되는 상수 표현식)예요.
- lvalue 참조 매개변수에 대해, 인스턴스화 시 제공된 인자는 임시 객체, 이름 없는 lvalue, 또는 연결이 없는 이름 있는 lvalue일 수 없어요(다시 말해 인자는 연결을 가져야 해요).
- 멤버 포인터에 대해, 인자는
&Class::Member로 표현된 멤버 포인터이거나 널 포인터·std::nullptr_t(C++11부터) 값으로 평가되는 상수 표현식이어야 해요. - 특히 이것은 문자열 리터럴, 배열 요소의 주소, 비정적 멤버의 주소를, 대응하는 비타입 템플릿 매개변수가 객체 포인터인 템플릿을 인스턴스화하는 템플릿 인자로 쓸 수 없음을 뜻해요.
(C++17부터) 참조나 포인터 타입의 비타입 템플릿 매개변수와, 클래스 타입 비타입 템플릿 매개변수 및 그 하위 객체(C++20부터) 안의 참조·포인터 타입 비정적 데이터 멤버는 다음을 참조하거나 그 주소일 수 없어요.
- (참조 초기화 중에 만들어진 것을 포함한) 임시 객체
- 문자열 리터럴
typeid의 결과- 미리 정의된 변수
__func__ - 위 중 하나의 하위 객체(비정적 클래스 멤버, 기저 하위 객체, 배열 요소를 포함, C++20부터)
template<const int* pci>
struct X {};
int ai[10];
X<ai> xi; // OK: 배열→포인터 변환과 cv 한정 변환
struct Y {};
template<const Y& b>
struct Z {};
Y y;
Z<y> z; // OK: 변환 없음
template<int (&pa)[5]>
struct W {};
int b[5];
W<b> w; // OK: 변환 없음
void f(char);
void f(int);
template<void (*pf)(int)>
struct A {};
A<&f> a; // OK: 오버로드 해석이 f(int)를 선택
template<class T, const char* p>
class X {};
X<int, "Studebaker"> x1; // error: 문자열 리터럴을 템플릿 인자로 사용
template<int* p>
class X {};
int a[10];
struct S
{
int m;
static int s;
} s;
X<&a[2]> x3; // error (C++20까지): 배열 요소의 주소
X<&s.m> x4; // error (C++20까지): 비정적 멤버의 주소
X<&s.s> x5; // OK: 정적 멤버의 주소
X<&S::s> x6; // OK: 정적 멤버의 주소
template<const int& CRI>
struct B {};
B<1> b2; // error: 템플릿 인자에 임시 객체가 필요할 것
int c = 1;
B<c> b1; // OK
타입 템플릿 인자 (Type template arguments)
타입 템플릿 매개변수에 대한 템플릿 인자는 type-id여야 해요. 불완전 타입을 가리킬 수도 있어요.
template<typename T>
class X {}; // 클래스 템플릿
struct A; // 불완전 타입
typedef struct {} B; // 이름 없는 타입에 대한 타입 별명
int main()
{
X<A> x1; // OK: 'A'는 타입을 가리킴
X<A*> x2; // OK: 'A*'는 타입을 가리킴
X<B> x3; // OK: 'B'는 타입을 가리킴
}
템플릿 템플릿 인자 (Template template arguments)
템플릿 템플릿 매개변수에 대한 템플릿 인자는 클래스 템플릿이나 템플릿 별명을 가리키는 id-expression이어야 해요.
인자가 클래스 템플릿일 때, 매개변수와 일치시킬 때는 기본 템플릿만 고려돼요. 부분 특수화가 있다면, 그것들은 이 템플릿 템플릿 매개변수에 기반한 특수화가 어쩌다 인스턴스화될 때만 고려돼요.
template<typename T> // 기본 템플릿
class A { int x; };
template<typename T> // 부분 특수화
class A<T*> { long x; };
// 템플릿 템플릿 매개변수 V를 가진 클래스 템플릿
template<template<typename> class V>
class C
{
V<int> y; // 기본 템플릿 사용
V<int*> z; // 부분 특수화 사용
};
C<A> c; // c.y.x는 int 타입, c.z.x는 long 타입
템플릿 템플릿 인자 A를 템플릿 템플릿 매개변수 P와 일치시키려면, P가 A보다 적어도 더 특수화되어야 해요(at least as specialized, 아래 참조). P의 매개변수 목록이 매개변수 팩을 포함하면, A의 템플릿 매개변수 목록의 0개 이상의 템플릿 매개변수(또는 매개변수 팩)가 그것에 일치돼요. (C++11부터)
형식적으로, 템플릿 템플릿 매개변수 P가 템플릿 템플릿 인자 A보다 적어도 더 특수화되었다는 것은, 두 함수 템플릿으로의 다음 재작성에 대해, P에 대응하는 함수 템플릿이 함수 템플릿의 부분 순서 규칙에 따라 A에 대응하는 함수 템플릿보다 적어도 더 특수화되었을 때를 말해요. A의 템플릿 매개변수 목록(기본 인자 포함)을 가진 발명된 클래스 템플릿 X가 주어졌을 때:
- 두 함수 템플릿 각각은 각각
P또는A와 같은 템플릿 매개변수를 가진다. - 각 함수 템플릿은, 그 함수 템플릿의 템플릿 매개변수들에 대응하는 템플릿 인자를 가진
X의 특수화인 타입의 단일 함수 매개변수를 가진다. 이때 함수 템플릿의 템플릿 매개변수 목록의 각 템플릿 매개변수PP에 대해 대응하는 템플릿 인자AA가 형성된다.PP가 매개변수 팩을 선언하면AA는 팩 확장PP...이고, 그렇지 않으면(C++11부터)AA는 id-expressionPP이다.
재작성이 무효 타입을 만들어내면, P는 A보다 적어도 더 특수화되지 않는다.
template<typename T>
struct eval; // 기본 템플릿
template<template<typename, typename...> class TT, typename T1, typename... Rest>
struct eval<TT<T1, Rest...>> {}; // eval의 부분 특수화
template<typename T1> struct A;
template<typename T1, typename T2> struct B;
template<int N> struct C;
template<typename T1, int N> struct D;
template<typename T1, typename T2, int N = 17> struct E;
eval<A<int>> eA; // OK: eval의 부분 특수화와 일치
eval<B<int, float>> eB; // OK: eval의 부분 특수화와 일치
eval<C<17>> eC; // error: C가 부분 특수화의 TT와 일치하지 않음
// TT의 첫 매개변수는 타입 템플릿 매개변수인데,
// 17은 타입을 가리키지 않기 때문
eval<D<int, 17>> eD; // error: D가 부분 특수화의 TT와 일치하지 않음
// TT의 두 번째 매개변수는 타입 매개변수 팩인데,
// 17은 타입을 가리키지 않기 때문
eval<E<int, float>> eE; // error: E가 부분 특수화의 TT와 일치하지 않음
// E의 세 번째 (기본) 매개변수는 비타입이기 때문
P0522R0 채택 전에는 A의 각 템플릿 매개변수가 P의 대응 템플릿 매개변수와 정확히 일치해야 했어요. 이 때문에 많은 합리적인 템플릿 인자가 받아들여지지 않았지요.
비록 이것이 아주 일찍 지적됐지만(CWG#150), 해결될 즈음엔 그 변경이 C++17 작업 초안에 적용되어 사실상 C++17 기능이 됐어요. 많은 컴파일러가 기본적으로 이것을 비활성화해요.
- GCC는 C++17 이전의 모든 언어 모드에서 기본적으로 비활성화하고, 그 모드들에서는 컴파일러 플래그를 설정해야만 활성화할 수 있어요.
- Clang은 모든 언어 모드에서 기본적으로 비활성화하고, 컴파일러 플래그로만 활성화할 수 있어요.
- Microsoft Visual Studio는 이것을 일반적인 C++17 기능으로 취급하고 C++17 이상의 언어 모드에서만 활성화해요(즉 기본 모드인 C++14 언어 모드에서는 지원하지 않음).
template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template<class... Types> class C { /* ... */ };
template<template<class> class P> class X { /* ... */ };
X<A> xa; // OK
X<B> xb; // P0522R0 이후 OK
// 이전에는 error: 정확히 일치하지 않음
X<C> xc; // P0522R0 이후 OK
// 이전에는 error: 정확히 일치하지 않음
template<template<class...> class Q> class Y { /* ... */ };
Y<A> ya; // OK
Y<B> yb; // OK
Y<C> yc; // OK
template<auto n> class D { /* ... */ }; // note: C++17
template<template<int> class R> class Z { /* ... */ };
Z<D> zd; // P0522R0 이후 OK: 템플릿 매개변수가
// 템플릿 인자보다 더 특수화됨
template<int> struct SI { /* ... */ };
template<template<auto> class> void FA(); // note: C++17
FA<SI>(); // Error
기본 템플릿 인자 (Default template arguments)
기본 템플릿 인자는 매개변수 목록에서 = 뒤에 지정돼요. 기본은 어떤 종류의 템플릿 매개변수(타입, 비타입, 템플릿)에든 지정될 수 있지만, 매개변수 팩에는 지정할 수 없어요. (C++11부터)
기본이 기본 클래스 템플릿, 기본 변수 템플릿(C++14부터), 별칭 템플릿의 템플릿 매개변수에 지정되면, 이후의 각 템플릿 매개변수는 기본 인자를 가져야 해요. 단 마지막 것은 템플릿 매개변수 팩일 수 있어요(C++11부터). 함수 템플릿에서는 기본 뒤에 오는 매개변수에 대한 제한이 없고, 매개변수 팩 뒤에는 기본이 있거나 함수 인자로부터 추론될 수 있는 타입 매개변수만 올 수 있어요. (C++11부터)
기본 매개변수는 허용되지 않아요.
- 클래스 템플릿 멤버의 클래스 외 정의에서(클래스 본문 안의 선언에 제공되어야 해요. 단, 비템플릿 클래스의 멤버 템플릿은 클래스 외 정의에서 기본 매개변수를 쓸 수 있어요. GCC bug 53856 참조)
- friend 클래스 템플릿 선언에서
- (C++11까지) 어떤 함수 템플릿 선언이나 정의에서
(C++11부터) friend 함수 템플릿 선언에서는, 그 선언이 정의이고 이 함수의 다른 선언이 이 번역 단위에 나타나지 않을 때만 기본 템플릿 인자가 허용돼요.
선언들에 나타나는 기본 템플릿 인자는 기본 함수 인자처럼 병합돼요.
template<typename T1, typename T2 = int> class A;
template<typename T1 = int, typename T2> class A;
// 위는 다음과 같음:
template<typename T1 = int, typename T2 = int> class A;
하지만 같은 매개변수에 같은 스코프에서 기본 인자를 두 번 줄 수는 없어요.
template<typename T = int> class X;
template<typename T = int> class X {}; // error
비타입 템플릿 매개변수의 기본 템플릿 인자를 파싱할 때, 첫 번째 비중첩 >는 greater-than 연산자가 아니라 템플릿 매개변수 목록의 끝으로 취해져요.
template<int i = 3 > 4> // 문법 오류
class X { /* ... */ };
template<int i = (3 > 4)> // OK
class Y { /* ... */ };
템플릿 템플릿 매개변수의 템플릿 매개변수 목록은 그 자신의 기본 인자를 가질 수 있고, 그것은 템플릿 템플릿 매개변수 자신이 스코프에 있는 곳에서만 효력이 있어요.
// 기본을 가진 타입 템플릿 매개변수가 있는 클래스 템플릿
template<typename T = float>
struct B {};
// 템플릿 템플릿 매개변수 T는, 기본을 가진 하나의 타입 템플릿 매개변수로
// 구성된 매개변수 목록을 가짐
template<template<typename = float> typename T>
struct A
{
void f();
void g();
};
// 클래스 외부 멤버 함수 템플릿 정의
template<template<typename TT> class T>
void A<T>::f()
{
T<> t; // error: TT에 스코프 밖에서 기본이 없음
}
template<template<typename TT = char> class T>
void A<T>::g()
{
T<> t; // OK: t는 T<char>
}
기본 템플릿 매개변수에 사용된 이름에 대한 멤버 접근은 사용 지점이 아니라 선언 시점에 검사돼요.
class B {};
template<typename T>
class C
{
protected:
typedef T TT;
};
template<typename U, typename V = typename U::TT>
class D: public U {};
D<C<B>>* d; // error: C::TT는 protected
(C++14부터) 기본 템플릿 인자는 그 기본 인자의 값이 필요할 때 암시적으로 인스턴스화돼요. 단 그 템플릿이 함수를 가리키는 데 쓰일 때는 예외예요.
template<typename T, typename U = int>
struct S {};
S<bool>* p; // U에 대한 기본 인자가 이 시점에 인스턴스화됨
// p의 타입은 S<bool, int>*
템플릿 인자 동등성 (Template argument equivalence)
템플릿 인자 동등성은 두 템플릿 식별자가 같은지 결정하는 데 쓰여요.
두 값이 같은 타입이고 다음 조건 중 하나를 충족하면 template-argument-equivalent예요.
- 정수나 열거형 타입이고 그 값이 같다.
- 포인터 타입이고 같은 포인터 값을 가진다.
- 멤버 포인터 타입이고 같은 클래스 멤버를 가리키거나 둘 다 널 멤버 포인터 값이다.
- lvalue 참조 타입이고 같은 객체나 함수를 가리킨다.
std::nullptr_t타입이다. (C++11부터)- 부동소수점 타입이고 그 값이 동일하다.
- 배열 타입이고(이 경우 배열은 어떤 클래스/공용체의 멤버 객체여야 해요, C++26까지) 대응하는 요소들이 template-argument-equivalent이다.
- 공용체 타입이고, 둘 다 활성 멤버가 없거나 같은 활성 멤버를 가지고 그 활성 멤버들이 template-argument-equivalent이다.
- 람다 클로저 타입이다.
- 비공용체 클래스 타입이고 대응하는 직접 하위 객체와 참조 멤버들이 template-argument-equivalent이다. (C++20부터)
std::meta::info타입이고 그 값이 같게 비교된다. (C++26부터)
참고 (Notes)
(C++20부터) 템플릿 매개변수에서 타입 제약은 auto가 있는지에 따라 타입 매개변수와 비타입 매개변수 둘 다에 쓰일 수 있어요.
template<typename>
concept C = true;
template<C, // 타입 매개변수
C auto // 비타입 매개변수
>
struct S{};
S<int, 0> s;
기능 검사 매크로(feature-test macro)는 다음과 같아요.
| 기능 검사 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_nontype_template_parameter_auto |
201606L | (C++17) | auto로 비타입 템플릿 매개변수 선언 |
__cpp_template_template_args |
201611L | (C++17)(DR) | 템플릿 템플릿 인자의 일치 |
__cpp_nontype_template_args |
201411L | (C++17) | 모든 비타입 템플릿 인자에 상수 평가 허용 |
| 201911L | (C++20) | 비타입 템플릿 매개변수의 클래스 타입과 부동소수점 타입 | |
__cpp_template_parameters |
202502L | (C++26) | 컨셉과 변수 템플릿 템플릿 매개변수 |
예제 (Examples)
비타입 템플릿 매개변수의 다양한 형태를 보여주는 코드예요.
#include <array>
#include <iostream>
#include <numeric>
// 단순한 비타입 템플릿 매개변수
template<int N>
struct S { int a[N]; };
template<const char*>
struct S2 {};
// 복잡한 비타입 예제
template
<
char c, // 정수 타입
int (&ra)[5], // 객체에 대한 lvalue 참조 (배열 타입)
int (*pf)(int), // 함수 포인터
int (S<10>::*a)[10] // 멤버 객체 포인터 (타입 int[10])
>
struct Complicated
{
// 컴파일 타임에 선택된 함수를 호출하고
// 그 결과를 컴파일 타임에 선택된 배열에 저장
void foo(char base)
{
ra[4] = pf(c - base);
}
};
// S2<"fail"> s2; // error: 문자열 리터럴은 사용할 수 없음
char okay[] = "okay"; // 연결을 가진 정적 객체
// S2<&okay[0]> s3; // error: 배열 요소는 연결이 없음
S2<okay> s4; // 동작함
int a[5];
int f(int n) { return n; }
// C++20: NTTP는 리터럴 클래스 타입일 수 있음
template<std::array arr>
constexpr
auto sum() { return std::accumulate(arr.cbegin(), arr.cend(), 0); }
// C++20: 클래스 템플릿 인자는 호출 지점에서 추론됨
static_assert(sum<std::array<double, 8>{3, 1, 4, 1, 5, 9, 2, 6}>() == 31.0);
// C++20: NTTP 인자 추론과 CTAD
static_assert(sum<std::array{2, 7, 1, 8, 2, 8}>() == 28);
int main()
{
S<10> s; // s.a는 10개 int의 배열
s.a[9] = 4;
Complicated<'2', a, f, &S<10>::a> c;
c.foo('0');
std::cout << s.a[9] << a[4] << '\n';
}
출력:
42
예제에서 눈여겨볼 점은, C++20부터 NTTP로 클래스 타입(std::array)을 직접 받을 수 있다는 거예요. sum<std::array<double, 8>{...}>처럼 호출 지점에서 템플릿 인자를 추론(CTAD)해서 넘기는 것도 가능해요.
결함 보고 (Defect reports)
다음의 동작 변경 결함 보고가 이전에 공개된 C++ 표준들에 소급 적용됐어요.
| DR | 적용 | 공개된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 150 (P0522R0) | C++98 | 템플릿 템플릿 인자는 템플릿 템플릿 매개변수의 매개변수 목록과 정확히 일치해야 했음 | 더 특수화된 것도 허용 |
| CWG 184 | C++98 | 템플릿 템플릿 매개변수의 템플릿 매개변수가 기본 인자를 가질 수 있는지가 불특정이었음 | 명세 추가 |
| CWG 354 | C++98 | 널 포인터 값이 비타입 템플릿 인자가 될 수 없었음 | 허용 |
| CWG 1398 | C++11 | 비타입 템플릿 인자가 std::nullptr_t 타입일 수 없었음 |
허용 |
| CWG 1570 | C++98 | 비타입 템플릿 인자가 하위 객체의 주소를 지정할 수 있었음 | 허용하지 않음 |
| CWG 1922 | C++98 | 이름이 injected-class-name인 클래스 템플릿이 이전 선언의 기본 인자를 쓸 수 있는지가 불명확했음 | 허용 |
| CWG 2032 | C++14 | 변수 템플릿에는 기본 인자가 있는 템플릿 매개변수 뒤의 템플릿 매개변수에 제한이 없었음 | 클래스 템플릿과 별칭 템플릿과 같은 제한 적용 |
| CWG 2542 | C++20 | 클로저 타입이 구조적인지가 불명확했음 | 구조적이지 않음 |
| CWG 2845 | C++20 | 클로저 타입이 구조적이지 않았음 | 캡처가 없으면 구조적 |
| P2308R1 | C++11/C++20 | 1. 리스트 초기화가 비타입 템플릿 인자에 허용되지 않았음(C++11) 2. 클래스 타입 비타입 템플릿 매개변수가 어떻게 초기화되는지가 불명확했음(C++20) | 1. 허용 2. 명확히 함 |
같이 보기 (See also)
is_structural(C++26): 타입이 구조적 타입인지 검사 (클래스 템플릿)is_structural_type(C++26): 반영된 타입이 구조적 타입인지 검사 (함수)
더 알아보기
- 템플릿: 템플릿 매개변수가 등장하는 전체 구조.
- 클래스 템플릿: 매개변수화된 클래스 정의.
- 함수 템플릿: 매개변수화된 함수 정의.
- 템플릿 특수화: 특정 인자 집합에 맞춘 템플릿.
- 매개변수 팩 (C++11): 가변 개수의 템플릿 인자 처리.