템플릿 매개변수와 템플릿 인자

템플릿 매개변수와 템플릿 인자 (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부터)
  1. 비타입 템플릿 매개변수.
  2. 기본 템플릿 인자를 가진 비타입 템플릿 매개변수.
  3. 비타입 템플릿 매개변수 팩.
  • 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-keytypename 또는 class. 타입 템플릿 매개변수 선언에서 이 두 키워드 사이에는 차이가 없어요.
  • type-constraint — 컨셉의 이름이거나, 컨셉의 이름 뒤에 (꺾쇠 안의) 템플릿 인자 목록이 이어지는 것. 어느 쪽이든 컨셉 이름은 선택적으로 한정될 수 있어요.
  • name — 타입 템플릿 매개변수의 이름
  • default — 기본 템플릿 인자
  1. 기본 없는 타입 템플릿 매개변수.
template<class T>
class My_vector { /* ... */ };
  1. 기본이 있는 타입 템플릿 매개변수.
template<class T = void>
struct My_op_functor { /* ... */ };
  1. 타입 템플릿 매개변수 팩.
template<typename... Ts>
class My_tuple { /* ... */ };
  1. 기본 없는 제약된(constrained) 타입 템플릿 매개변수.
template<My_concept T>
class My_constrained_vector { /* ... */ };
  1. 기본이 있는 제약된 타입 템플릿 매개변수.
template<My_concept T = void>
class My_constrained_op_functor { /* ... */ };
  1. 제약된 타입 템플릿 매개변수 팩.
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> && ...)이에요.
  • QC<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-keyclass 또는(C++17부터) typename
  1. 선택적 이름을 가진 템플릿 템플릿 매개변수.
  2. 선택적 이름과 기본을 가진 템플릿 템플릿 매개변수.
  3. 선택적 이름을 가진 템플릿 템플릿 매개변수 팩.

템플릿 선언의 본문에서 이 매개변수의 이름은 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로도 표시됨). Pv와 template-argument-equivalent인 불특정 후보 초기화로부터 복사 초기화돼요.
  • v의 수명은 vP의 초기화 직후 끝나요.
  • P의 초기화가 다음 조건 중 하나를 충족하면 프로그램은 ill-formed예요.
    • 초기화가 ill-formed일 것.
    • 초기화를 위한 발명된 declarator-initializer 시퀀스의 전체 표현식이, manifestly constant-evaluated 표현식으로 해석될 때 상수 표현식이 아닐 것.
    • 초기화가 Pv와 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와 일치시키려면, PA보다 적어도 더 특수화되어야 해요(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-expression PP이다.

재작성이 무효 타입을 만들어내면, PA보다 적어도 더 특수화되지 않는다.

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): 가변 개수의 템플릿 인자 처리.