멤버 템플릿

멤버 템플릿 (Member templates)

클래스 안의 멤버 함수, 멤버 변수, 그리고 중첩 클래스 모두가 템플릿이 될 수 있다는 사실, 알고 계셨나요? 클래스 자체가 템플릿이 아니어도 멤버만 템플릿으로 만들 수 있어요.

출처: cppreference

본문

템플릿 선언(class, function, 그리고 C++14부터는 variable)은 지역 클래스(local class)가 아닌 어떤 클래스, 구조체, 공용체의 멤버 지정(member specification) 안에 나타날 수 있어요.

먼저 멤버 템플릿이 실제로 어떻게 쓰이는지 간단한 예시부터 볼게요.

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

struct Printer
{
    // generic functor
    std::ostream& os;
    Printer(std::ostream& os) : os(os) {}
    template<typename T>
    void operator()(const T& obj) { os << obj << ' '; } // 멤버 템플릿
};

int main()
{
    std::vector<int> v{1,2,3};
    std::for_each(v.begin(), v.end(), Printer(std::cout));
    std::string s{"abc"};
    std::ranges::for_each(s, Printer(std::cout));
}

출력:

1 2 3 a b c

Printer는 클래스 템플릿이 아닌데도 operator()가 멤버 템플릿이라서, int 요소와 char 요소를 모두 처리할 수 있는 걸 볼 수 있어요.

멤버 템플릿의 부분 특수화(partial specialization)는 클래스 스코프와 바깥 네임스페이스 스코프 양쪽에 나타날 수 있어요. 명시적 특수화(explicit specialization)는 기본 템플릿(primary template)이 나타날 수 있는 어떤 스코프에서든 나타날 수 있죠.

struct A
{
    template<class T> struct B;        // 기본 멤버 템플릿
    template<class T> struct B<T*> {}; // OK: 부분 특수화
//  template<> struct B<int*> {};      // OK via CWG 727: 전체 특수화
};
template<> struct A::B<int*> {};       // OK
template<class T> struct A::B<T&> {};  // OK

바깥 클래스 선언이, 다시 말해 클래스 템플릿이라면, 멤버 템플릿을 클래스 본문 밖에서 정의할 때 두 세트의 템플릿 매개변수를 받아요. 하나는 바깥 클래스용, 또 하나는 멤버 템플릿 자신용이에요:

template<typename T1>
struct string
{
    // 멤버 템플릿 함수
    template<typename T2>
    int compare(const T2&);
    // 생성자도 템플릿이 될 수 있다
    template<typename T2>
    string(const std::basic_string<T2>& s) { /*...*/ }
};
// string<T1>::compare<T2>의 클래스 밖 정의
template<typename T1> // 바깥 클래스 템플릿용
template<typename T2> // 멤버 템플릿용
int string<T1>::compare(const T2& s) { /* ... */ }

멤버 함수 템플릿 (Member function templates)

소멸자(destructor)와 복사 생성자(copy constructor)는 템플릿이 될 수 없어요. 복사 생성자의 타입 시그니처로 인스턴스화될 수 있는 템플릿 생성자가 선언되면, 암시적으로 선언된 복사 생성자가 대신 사용돼요.

멤버 함수 템플릿은 가상(virtual)이 될 수 없고, 파생 클래스의 멤버 함수 템플릿은 기본 클래스의 가상 멤버 함수를 오버라이드할 수 없어요.

class Base
{
    virtual void f(int);
};

struct Derived : Base
{
    // 이 멤버 템플릿은 Base::f를 오버라이드하지 않는다
    template<class T> void f(T);

    // 템플릿이 아닌 멤버 오버라이드는 템플릿을 호출할 수 있다:
    void f(int i) override
    {
         f<>(i);
    }
};

같은 이름을 가진 템플릿이 아닌 멤버 함수와 템플릿 멤버 함수가 함께 선언될 수 있어요. 충돌이 생기는 경우, 즉 어떤 템플릿 특수화가 템플릿이 아닌 함수의 시그니처와 정확히 일치할 때는, 명시적 템플릿 인자 목록이 제공되지 않는 한 그 이름과 타입의 사용은 템플릿이 아닌 멤버를 가리켜요.

template<typename T>
struct A
{
    void f(int); // 템플릿이 아닌 멤버

    template<typename T2>
    void f(T2); // 멤버 템플릿
};

// 템플릿 멤버 정의
template<typename T>
template<typename T2>
void A<T>::f(T2)
{
    // some code
}

int main()
{
    A<char> ac;
    ac.f('c'); // 템플릿 함수 A<char>::f<char>(char) 호출
    ac.f(1);   // 템플릿이 아닌 함수 A<char>::f(int) 호출
    ac.f<>(1); // 템플릿 함수 A<char>::f<int>(int) 호출
}

멤버 함수 템플릿을 클래스 밖에서 정의할 때는 클래스 안의 선언과 동등(equivalent)해야 해요. 동등성의 정의는 함수 템플릿 오버로딩에서 설명하는데, 그렇지 않으면 오버로드로 간주돼요.

struct X
{
    template<class T> T good(T n);
    template<class T> T bad(T n);
};

template<class T> struct identity { using type = T; };

// OK: 동등한 선언
template<class V>
V X::good(V n) { return n; }

// 오류: X 안의 어떤 선언과도 동등하지 않음
template<class T>
T X::bad(typename identity<T>::type n) { return n; }

변환 함수 템플릿 (Conversion function templates)

사용자 정의 변환 함수도 템플릿이 될 수 있어요.

struct A
{
    template<typename T>
    operator T*(); // 모든 타입에 대한 포인터로의 변환
};

// 클래스 밖 정의
template<typename T>
A::operator T*() { return nullptr; }

// char*에 대한 명시적 특수화
template<>
A::operator char*() { return nullptr; }

// 명시적 인스턴스화
template A::operator void*();

int main()
{
    A a;
    int* ip = a.operator int*(); // A::operator int*()의 명시적 호출
}

오버로드 해석(overload resolution) 중에 변환 함수 템플릿의 특수화는 이름 탐색으로 찾아지지 않아요. 대신 모든 보이는 변환 함수 템플릿이 고려되고, 템플릿 인자 추론(변환 함수 템플릿에 대한 특별한 규칙이 있는)으로 만들어진 모든 특수화가 마치 이름 탐색으로 찾아진 것처럼 사용돼요.

파생 클래스의 using 선언(using-declaration)은 기본 클래스의 템플릿 변환 함수 특수화를 가리킬 수 없어요.

사용자 정의 변환 함수 템플릿은 추론된 반환 타입을 가질 수 없어요:

struct S {
    operator auto () const { return 10; } // OK
    template<class T>
    operator auto () const { return 42; } // error
};

(C++14부터)

멤버 변수 템플릿 (Member variable templates)

변수 템플릿 선언은 클래스 스코프에 나타날 수 있는데, 이 경우 정적 데이터 멤버 템플릿(static data member template)을 선언해요. 자세한 내용은 변수 템플릿을 참고하세요. (C++14부터)

결함 보고 (Defect reports)

다음 동작을 바꾸는 결함 보고(defect reports)가 이전에 발표된 C++ 표준에 소급 적용됐어요.

DR 적용 대상 발표 당시 동작 올바른 동작
CWG 1878 C++14 operator auto가 기술적으로 허용됨 operator auto 금지

더 알아보기