8 분 소요

extern 템플릿

기존에는 템플릿을 헤더 파일에 정의해 두고, 여러 cpp 파일에서 #include 했었는데요, 이러면 템플릿 정의가 중복되어 코드 크기가 커집니다.(템플릿 인스턴스 중복 생성 참고)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ----
// Test_ModernCpp_ExternTemplate.h 에서
// ----
namespace ExternTemplate {
    template<typename T>
    T Add(T a, T b) {
        return a + b;
    }
}

// ----
// test1.cpp 에서
// ----
#include "Test_ModernCpp_ExternTemplate.h"

TEST(TestModern, ExternTemplate1) {
    using namespace ExternTemplate;

    EXPECT_TRUE(Add(1, 2) == 3); // Add<int>()가 정의 되어 포함됩니다.
}

// ----
// test2. cpp 에서
// ----
#include "Test_ModernCpp_ExternTemplate.h"

TEST(TestModern, ExternTemplate2) {
    using namespace ExternTemplate;

    EXPECT_TRUE(Add(10, 20) == 30); // Add<int>가 재정의 되어 포함됩니다.
}

export 템플릿이 있기는 했지만, 제대로 구현한 컴파일러도 드물고 의견도 일치하지 않아 C++11 부터 완전히 remove되었고,

C++11 부터는 extern 템플릿을 추가하여 템플릿 선언만 할 수 있으며, 불필요한 코드 크기를 최소화 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// ----
// Test_ModernCpp_ExternTemplate.h 에서
// ----
namespace ExternTemplate {
    template<typename T>
    T Add(T a, T b) {
        return a + b;
    }
}

// ----
// test1.cpp 에서
// ----
#include "Test_ModernCpp_ExternTemplate.h"

TEST(TestModern, ExternTemplate1) {
    using namespace ExternTemplate;

    EXPECT_TRUE(Add(1, 2) == 3); // Add<int>()가 정의 되어 포함됩니다.
}

// ----
// test2. cpp 에서
// ----
#include "Test_ModernCpp_ExternTemplate.h"

// 템플릿 선언만 합니다. 이전에 정의된 템플릿을 사용합니다.
extern template int ExternTemplate::Add<int>(int, int); // C++11

TEST(TestModern, ExternTemplate2) {
    using namespace ExternTemplate;

    EXPECT_TRUE(Add(10, 20) == 30); 
}

템플릿 오른쪽 꺽쇠 괄호

템플릿 인스턴스화템플릿 개체로 인스턴스화 하면 >이 중첩됩니다.

기존에는 이게 비트 Right Shift 연산자 >> 로 파싱되어 컴파일 되지 않았습니다. 따라서, vector<A<int> >와 같이 억지로 띄어쓰기를 했는데요(템플릿 파싱 오류 참고),

C++11 부터는 파싱을 개선하여 vector<A<int>>와 같이 >>을 사용해도 됩니다.

1
2
3
4
5
template<typename T>
class A {};

std::vector<A<int> > a; // C++03. 공백을 추가해야 했습니다.
std::vector<A<int>> b_11; // C++11/ 공백이 없어도 됩니다.

(C++14~) 변수 템플릿

기존의 템플릿클래스와 함수만 지원했는데요(템플릿 참고),

C++14 부터는 변수도 템플릿으로 만들 수 있습니다.

다음 예는 동일한 값을 지정한 pi를 타입에 따라 다른 정밀도로 사용하는 예입니다.

1
2
3
4
5
6
7
8
template<class T>
// constexpr T pi_14{3.1415926535897932385L}; // (X) 컴파일 오류. 중괄호 초기화는 암시적 형변환이 안되서 = 로 초기화 합니다.
constexpr T pi_14 = 3.1415926535897932385L; 

// pi_14를 타입에 따라 서로 다른 정밀도로 사용할 수 있습니다.
std::cout << "pi_14<double> : " << std::setprecision(10) << pi_14<double> << std::endl;
std::cout << "pi_14<float> : " << std::setprecision(10) <<pi_14<float> << std::endl;
std::cout << "pi_14<int> : " << std::setprecision(10) <<pi_14<int> << std::endl;
1
2
3
pi_14<double> : 3.141592654
pi_14<float> : 3.141592741 // double 보다 정밀도가 낮습니다.
pi_14<int> : 3

또한, 템플릿 특수화를 이용하여 템플릿 메타 프로그래밍에 활용할 수 있습니다.

다음은 템플릿 메타 프로그래밍에서 소개한 Factorial변수 템플릿으로 구현한 예입니다.

1
2
3
4
5
6
7
8
9
// 1을 뺀 값을 재귀 호출합니다.
template<int val> 
constexpr int factorial_14 = val * factorial_14<val - 1>; 

// 1일때 특수화 버전. 더이상 재귀 호출을 안합니다.
template<>
constexpr int factorial_14<1> = 1;

EXPECT_TRUE(factorial_14<5> == 5 * 4 * 3 * 2 * 1);

(C++17~) 클래스 템플릿 인수 추론

기존에는 함수 템플릿만 인수를 추론하고 클래스 템플릿은 인수를 추론하지 않았는데요(함수 템플릿 인수 추론 참고),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<typename T>
T Func(T a, T b) {return a + b;}

EXPECT_TRUE(Func(1, 2) == 3); // 인수 1, 2 로부터 추론해서 사용합니다.

template<typename T>
class A {
public:
    explicit A(T val) {}
    T Func(T a, T b) {return a + b;}
};

A<int> a{10}; // ~C++17 이전에는 타입을 명시해야 합니다.
EXPECT_TRUE(a.Func(1, 2) == 3);

C++17 부터는 클래스 템플릿도 생성자에 전달된 인수를 추론하므로 다음과 같이 사용할 수 있습니다.

1
2
A a_17{10}; // C++17 이후부터는 인수로부터 추론합니다.
EXPECT_TRUE(a_17.Func(1, 2) == 3); 

(C++20~) 클래스 템플릿 인수 추론시 initializer_list인 경우가 개선되어 std::vector v_20{1, 2, 3};처럼 템플릿 인자를 명시하지 않아도 됩니다.

(C++17~) 클래스 템플릿 인수 추론 사용자 정의 가이드

C++17 에서는 클래스 템플릿 인수 추론 사용자 정의 가이드가 추가되어 클래스 템플릿 인수 추론시 컴파일러에게 가이드를 줄 수 있습니다.

예를들어 다음의 클래스 A는 타입이 T로 동일한 인자 xy를 전달받습니다. 따라서, A a_17{1, 2};A<int>로 추론하여 사용할 수 있는데요, A b_17{1, 1.5};는 컴파일 오류가 발생합니다. A<int, double>로 추론되어 T의 타입이 intdouble로 서로 다르니까요.

1
2
3
4
5
6
7
8
9
template<typename T>
class A {
public:
    A(T x, T y) {}
};  

A a_17{1, 2}; // A<int>. 인수로부터 인자를 추론합니다.
// A b_17{1, 1.5}; // (X) 컴파일 오류. A<int, double>은 안됩니다.
A<double> c{1, 1.5}; // 명시적으로 A<double>을 사용합니다.

이럴때 클래스 템플릿 인수 추론시 컴파일러에게 가이드를 줄 수 있습니다.

사용자 정의 가이드 형식은 다음과 같습니다.

1
2
template<템플릿 인자>
템플릿명(생성자 인자) -> 템플릿명<인스턴스화할 타입>; 

다음 예는 상기 예에서 그냥 2번째 템플릿 인자 타입으로 클래스 템플릿 인수 추론을 하도록 가이드한 예입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T>
class A {
public:
    A(T x, T y) {}
};  

// 클래스 템플릿 인수 추론 사용자 정의 가이드
template<typename T, typename U>
A(T x, U y) -> A<U>; // T와 U가 타입이 다르다면 U 타입으로 추론하세요. 
 
A a_17{1, 2}; 
A b_17{1, 1.5}; // (O) 클래스 템플릿 인수 추론 사용자 정의 가이드에 의해 A<double>로 추론합니다.
A<double> c{1, 1.5}; 

(C++17~) 비타입 템플릿 인자의 auto 허용

C++17 부터는 비타입 템플릿 인자에서 auto를 허용합니다.

기존에는 임의 타입의 개체를 템플릿 인자로 사용할때, 다음과 같이 타입(T)과 개체(val)를 같이 전달받았는데요,(템플릿 인자 참고)

1
2
3
4
5
6
7
8
template<typename T, T val> // 타입과 개체를 전달받습니다.
class A {
public:
    T GetVal() {return val;} 
};

A<int, 10> a{};
EXPECT_TRUE(a.GetVal() == 10);

C++17 부터는 auto를 이용하여 임의 타입 개체를 전달받을 수 있습니다.

1
2
3
4
5
6
7
8
template<auto val> // 비타입 템플릿 인자인 경우 auto를 사용할 수 있습니다.
class A_17 {
public:
    auto GetVal() const {return val;}
};  

A_17<10> a_17{};
EXPECT_TRUE(a_17.GetVal() == 10);

(C++20~) 비타입 템플릿 인자 규칙이 완화되어 실수 타입과 리터럴 타입을 사용할 수 있습니다.

(C++20~) 축약된 함수 템플릿

기존에는 함수 인자auto를 사용할 수 없었고, C++14 부터 일반화된 람다 표현식에서만 auto를 사용할 수 있었습니다.(함수 인자, auto, 일반화된 람다 표현식 참고)

C++20 부터는 축약된 함수 템플릿이 추가되어 함수 인자auto를 사용할 수 있습니다. 사실상 함수 템플릿의 간략한 표현입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// template<typename T> 
// void f(T param);
void f_20(auto param) {} 
f_20(10);

// template<typename T, typename U>
// void g(T param1, U param2);
void g_20(auto param1, auto param2) {} // param1과 param2는 동일한 타입이라는 보장이 없습니다.
g_20(10, 3.14);

// template<typename... Params>
// void h_11(Params... params);
void h_20(auto... params) {} // 가변 템플릿도 지원합니다.
h_20(10, 3.14, "Hello");

// template<typename T, typename U>
// void i(T param1, U param2) {}
template<typename T>
void i_20(T param1, auto param2) {} // 템플릿 내에서 혼합해서 사용할 수 있습니다.
i_20(10, 3.14);

autodecltype(auto)가변 인자를 함께 사용할 수도 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
int Sum_20(auto count, ...) { // 가변 인자를 사용하는 함수입니다.

    int result = 0;
    std::va_list paramList; // 가변 인자
    va_start(paramList, count); // 가변 인자 처리 시작
    for (int i = 0; i < count; ++i) {
        result += va_arg(paramList, int); // 가변 인자 추출
    }
    va_end(paramList); // 가변 인자 처리 끝
    return result;       
}

EXPECT_TRUE(Sum_20(3, 1, 2, 3) == 1 + 2 + 3);

(C++20~) 비타입 템플릿 인자 규칙 완화

기존에는 비타입 템플릿 인자를 사용할 경우, 정수 타입, 열거형 상수의 열거자, 전역 또는 정적 개체의 포인터/참조자를 사용할 수 있었는데요(템플릿 인자 참고),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<int val>
class A {};
A<10> a; // 정수 타입

enum MyEnum{Val};
template<enum MyEnum myEnum>
class B {};
B<Val> b; // 열거형의 열거자

class MyClass {};
template<MyClass* ptr>
class C {};

template<MyClass& ref>
class D {};  

MyClass g_MyClass;
C<&g_MyClass> c; // 전역, 정적 개체의 포인터
D<g_MyClass> d; // 전역, 정적 개체의 참조자

C++20 부터는 비타입 템플릿 인자 규칙이 완화되어

사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
template<double d> 
class A_20 {};
A_20<3.14> a; // 실수 타입

class MyClass_11 {
public: 
    int m_Val; // 멤버 변수는 public이고, mutable이 아니어야 합니다.
public: 
    constexpr explicit MyClass_11(int val) : m_Val(val) {} // constexpr 생성자여야 합니다.
};
template<MyClass_11 myClass> 
class B_20 {};
B_20<MyClass_11{1}> b; // 멤버 변수는 public이고, mutable이 없으며, constexpr 생성자가 있는 리터럴 타입

(C++20~) 클래스 템플릿 인수 추론시 initializer_list 개선

C++17 부터 클래스 템플릿 인수 추론이 추가(클래스 템플릿 인수 추론 참고) 되었으나, initializer_list로 초기화시에는 클래스 템플릿 인수 추론이 되지 않았습니다. 따라서, std::vector<int> v_11{1, 2, 3};와 같이 템플릿 인자를 명시해야 했는데요,

C++20 부터는 클래스 템플릿 인수 추론시 initializer_list인 경우가 개선되어 std::vector v_20{1, 2, 3};처럼 템플릿 인자를 명시하지 않아도 됩니다.

1
2
3
4
5
6
7
8
9
10
11
template<typename T>
class A {
public:
    explicit A(T val) {}
};

A<int> a{10}; // ~C++17 이전에는 타입을 명시해야 합니다.
A a_17{10}; // C++17 부터는 인수 타입으로부터 추론합니다.

std::vector<int> v_11{1, 2, 3}; // ~C++20 이전에는 initializer_list로 초기화시에 타입을 명시해야 합니다.
std::vector v_20{1, 2, 3}; // C++20 부터는 initializer_list로 초기화시에 타입을 명시하지 않아도 됩니다.

태그:

카테고리:

업데이트:

댓글남기기