타입이나 값을 매개변수로 받아, 컴파일 시점에 실제 인자에 맞는
코드를 생성한다.

template<typename T>
T Max(T a, T b)
{
    return (a > b) ? a : b;
}

int x = Max(3, 5); // T = int
// int Max(int a, int b)
double y = Max(2.1, 4.3); // T = double
// double Max(double a, double b)

template parameter에서 typename, class 둘다 같은 의미로 사용된다.

template <typename T>
template <class T>

template parameter는 여러개를 선언할 수 있다.

template <typename T, typename U>

기본 template arguments를 가질 수 있음.

template <class T, class Allocator = allocator<T>> class vector;
vector<int> myInts;
vector<int, MyAllocator> ints;

template의 정의가 header에 있으면,
headerinclude하여 사용하는 각 번역 단위에서
필요한 인스턴스화가 일어날 수 있다.

//Template.h
template <typename T>
T HeaderInclude(T a, T b)
{
    return a + b;
}

//Otheruse1.cpp
#include "Template.h"

int func() { return HeaderInclude(10, 20); }

//Main.cpp
#include "Template.h"
#include <iostream>
void main()
{
    std::cout << HeaderInclude(10, 10) << '\n';
    return;
}
; Otheruse1.obj
021 00000000 SECT4  notype ()    External     | ??$HeaderInclude@H@@YAHHH@Z (int __cdecl HeaderInclude<int>(int,int))

; Main.obj
021 00000000 SECT4  notype ()    External     | ??$HeaderInclude@H@@YAHHH@Z (int __cdecl HeaderInclude<int>(int,int))

일반 함수 정의를 header에 두고 여러 번역 단위에서 include하면
각 번역 단위에 동일한 함수 정의가 생긴다.

이 함수가 inline이 아니면 ODR 위반으로 인해
multiple definition 링크 에러가 발생한다.

template은 동일한 인스턴스가 여러 TU에서 생성될 수 있다.
구현은 여러개 중 하나만 선택된다.

00E 00000000 SECT4  notype       Static       | .text$mn
    Section length   3C, #relocs    2, #linenums    0, checksum  3CD53B0, selection    2 (pick any)
...

021 00000000 SECT4  notype ()    External     | ??$HeaderInclude@H@@YAHHH@Z (int __cdecl HeaderInclude<int>(int,int))

.obj단 에서는 inline 함수와 동일한 symbol을 갖는다.


명시적 인스턴스

template의 정의는 instantiation(인스턴스화)이 일어나는 번역 단위에서 보여야 한다.

//template.h
template <typename T>
T CppInclude(T a, T b);

//template.cpp
#include "Template.h"

template <typename T>
T CppInclude(T a, T b) { return a + b; }

// otheruse.cpp
#include "template.h"

int main()
{
    std::cout << CppInclude(10, 10); // LNK2019
    return 0;
}

번역 단위는 각각 독립적으로 컴파일되기 때문에
otheruse.cpp를 컴파일하는 시점에는 template.cpp에 있는
template 정의를 볼 수 없다.
그 결과 otheruse.cpp에서 인스턴스화가 실패하고, 정의된 심볼이 없어서
링크 에러가 발생했다.

.cpp에서 template 정의를 두고 사용하려면,
명시적 인스턴스화를 통해 함수 심볼을 생성해야 한다.
그러면 다른 번역 단위에서는 그 심볼을 일반 외부 함수처럼 링크해서 사용할 수 있다.

//template.h
template <typename T>
T CppInclude(T a, T b);

//template.cpp
#include "Template.h"

template <typename T>
T CppInclude(T a, T b) { return a + b; }

template int CppInclude<int>(int a, int b);
// 이 코드는 아래와 같이 생성된다.
int CppInclude<int>(int a, int b)
{
    return a + b;
}
020 00000000 SECT4  notype ()    External     | ??$CppInclude@H@@YAHHH@Z (int __cdecl CppInclude<int>(int,int))

명시적 인스턴스화는 class template에도 사용 가능하다.

template class A<int>;

Class Template

타입을 나중에 정해서 같은 구조의 클래스를 여러 타입으로 재사용할 수 있게 해준다.

template <typename T>
class A
{
private:
    T value;

public:
    void Set(T v) { value = v; }
    T Get() const { return value; }
}

A<int> int_a;
A<double> double_a;

class template으로 만들어진 클래스는 template argument가 다르면 서로 다른 클래스이다.

A<int> int_a;
A<double> double_a = int_a; // ❌

A<int>* p = nullptr;
A<double>* q = p; // ❌

// 둘은 이렇게 만들어진거나 다름없음
class Box_int { int value; }
class Box_double { double value; }

template class 밖에서 멤버 함수를 정의하려면

template <typename T>
void A<T>::Set(T v) { value = v; }

template <typename T>
T A<T>::Get() { return value; }

위 와같이 template argument가 들어간 형태로 작성해야 한다.
A는 template 이름이고 실제 클래스 이름이 아님

함수 template은 호출할 때 template argument을 명시하지 않아도
타입 추론이 이루어지는데
class templatetemplate argument를 직접 명시해야한다.

template <typename T>
void func(T v) {}

A<int> a;
// A a; X
// C++17부터 생성자에 타입을 추론할 수 있게 하면 가능함
// A a(10);

func<int>(3); // ⭕
func(3);      // ⭕

class template의 static 멤버는 타입별로 따로 존재한다.

template <typename T>
class A
{
public:
    static int count;
}
A<int>::count;
A<double>::count;

위 변수는 서로 다른 변수임.


Template Specialization(특수화)

특정 타입에 특정한 구현을 제공하는 기능

  • 완전특수화

    template<>
    class A<bool>
    {
    public:
      void Set(bool v) { // bool 타입에 특정된 코드 }
    }

    완전 특수화를 하려면 기본 template class가 있어야한다.

    template <typename T>
    class A
    {
    public:
       void Set(T v) {}
    }
  • 함수 Template 특수화

    template <>
    const char* Max(const char* a, const char* b)
    {
      return (strcmp(a, b) > 0) ? a : b;
    }

    함수 Template 특수화Overload와 겹친다.

    template <typename T>
    void func(T v) { std::cout << "base template\n"; }
    
    template <>
    void func(A v) { std::cout << "A specialization\n"; }
    
    void func(A v) { std::cout << "A overload\n"; }
    A a;
    func<A>(a);
    func(a);
    A specialization
    A overload

    함수 template 특수화Overload 해석과 함께 고려된다.
    명시적으로 template argument를 적지 않으면 일반 Overload가 선택될 수 있다.

  • 부분특수화, 클래스 template에만 가능하다.
    완전히 특정 타입을 고정하지 않는 경우.

    #include <stdio.h>
    
    template <class T> struct PTS {
      enum {
          IsPointer = 0,
          IsPointerToDataMember = 0
      };
    };
    
    template <class T> struct PTS<T*> {
      enum {
          IsPointer = 1,
          IsPointerToDataMember = 0
      };
    };
    
    template <class T, class U> struct PTS<T U::*> {
      enum {
          IsPointer = 0,
          IsPointerToDataMember = 1
      };
    };
    struct S { public: int x; };
    
    int main() {
      printf_s("PTS<S>::IsPointer == %d \nPTS<S>::IsPointerToDataMember == %d\n",
          PTS<S>::IsPointer, PTS<S>::IsPointerToDataMember);
    
      printf_s("PTS<S*>::IsPointer == %d \nPTS<S*>::IsPointerToDataMember == %d\n"
          , PTS<S*>::IsPointer, PTS<S*>::IsPointerToDataMember);
    
      printf_s("PTS<int S::*>::IsPointer == %d \nPTS"
          "<int S::*>::IsPointerToDataMember == %d\n",
          PTS<int S::*>::IsPointer, PTS<int S::*>::
          IsPointerToDataMember);
    }

    https://learn.microsoft.com/ko-kr/cpp/cpp/template-specialization-cpp?view=msvc-170
    msdn의 class template partial specialization을 보자.
    여기서 S::*, T U::*는 클래스의 멤버를 가리키는 멤버 포인트 타입이다.
    각 타입에 따라 enum값을 구분했고 타입에 맞게 부분특수화가 작동한다.

    PTS<S>::IsPointer == 0
    PTS<S>::IsPointerToDataMember == 0
    PTS<S*>::IsPointer == 1
    PTS<S*>::IsPointerToDataMember == 0
    PTS<int S::*>::IsPointer == 0
    PTS<int S::*>::IsPointerToDataMember == 1

Non-Type Template Parameter(비형식 템플릿 매개변수)

타입이 아닌 값을 template parameter로 사용하는 문법

template <typename T, int N>
class A
{
    T data[N];
}

A<int, 10> a;

template argument로 사용할 수 있는 형태의 상수 표현이어야 한다

  • 정수
  • enum
  • 포인터
  • 참조
  • constexpr 객체
  • (C++ 20) class 타입 일부가 올 수 있다.

기본 인자도 사용 가능

template<typename T = int, int i = 10>
class A
{
    ...
}

A<int, 3> a;
A<> b;          // A<int, 10> b;
A<double> c; // A<double, 10> c;

typename

typename은 템플릿 문법에서 사용되는 키워드로

  1. template parameter가 타입임을 선언할 때
  2. dependent name이 타입임을 컴파일러에게 명시할 때

사용된다.

  • template parameter가 타입임을 선언

    // T는 타입
    template <typename T>
    void f();
  • dependent name이 타입임을 컴파일러에게 명시
    template parameter에 종속된 이름(dependent name)은
    기본적으로 타입이 아닌 이름(non-type)으로 해석된다.

    class A
    {
    public:
      using value_type = int;
    };
    
    // T가 뭐냐에 따라 의미가 달라지는 코드
    // dependent name(종속 이름)
    template <typename T>
    void func()
    {
      T::value_type x;
       // C7510 'value_type': use of dependent type name must be prefixed with 'typename'
      x = 10;
      std::cout << x;
    }
    
    func<A>();

    T::value_type x는 타입 선언으로 해석되지 않아 에러가 발생한다.
    그래서 타입으로 사용한다고 명시적으로 알려주어야 에러가 발생하지 않는다.

    template <typename T>
    void func()
    {
      typename T::value_type x;
      x = 10;
      std::cout << x;
    }

    template을 쓰는 멤버를 dependent name으로 호출할 때
    typename만으로는 모호하다.

    template <typename T>
    struct X
    {
      // ❌ 경우에 따라 파싱이 깨질 수 있음
      using R = typename T::rebind<int>::other;
    };

    rebind<를 template argument로 보지 않고 비교 연산자로 해석 될 수 있음.
    이 경우 구문에 template을 붙여 template argument라고 완전 명시한다.

    template <typename T>
    struct X
    {
      using R = typename T::template rebind<int>::other;
    };

    template parameter는 가변 parameter를 지원한다.

    template<typename... Arguments> class vtclass;
    vtclass< > vtinstance1;
    vtclass<int> vtinstance2;
    vtclass<float, bool> vtinstance3;

Template class friend

특정 template class나 template function이 private/protected에 접근할 수 있게 허용

template <typename T>
class A
{
private:
    T data;
public:
    // A의 typename과 같아도 되지만
    // 같으면 읽기 어려움
    // typename의 이름이 같아도 타입은 다름
    template <typename U> 
    friend class B;

    template <class U>
    friend T func(A<U> v);
}

template <class T>
class B
{
public:
    void func(A<U> a)
    {
        printf_s("%d\n", a.data); // ⭕
    }
};

template <class T>
T func(A<T> v)
{
    return v.data;  // ⭕
}
A<int> a;
a.Set(10);

B<int> bi;
B<double> bd;

bi.func(a);
bd.func(a);    // ❌

func(a);
func<int>(a);
func<double>(a); // ❌

Template Meta Programming

template의 코드가 컴파일 타임에 만들어지는걸 이용한 패턴
컴파일 타임 계산을 통해 상수 계산, 배열 크기 결정, 타입 선택,
조건 분기 등을 런타임 비용 없이 처리할 수 있다.
부분/완전 특수화를 통해 조건을 분기 할 수 있고, 타입 기반이어서 안전성을 보장한다.

template <bool B, typename T, typename F>
struct Conditional
{
    using type = T; // B가 true이면 T 선택
};

template <typename T, typename F>
struct Conditional<false, T, F>
{
    using type = F; // B가 false이면 F 선택
};

int main()
{
    Conditional<(sizeof(int) > 2), int, double>::type a; // int
    std::cout << sizeof(a) << "\n";
}

컴파일 타임에 코드가 만들어져서 컴파일 시간이 증가한다.
일반 코드보다 이해에 시간이 소요된다.


Curiously Recurring Template Pattern(CRTP)

파생 클래스가 자신의 타입을 template argument로 부모 클래스에 전달하는 패턴

template <typename T>
class Base {};

class Derived : public Base<Derived> {};
template <typename T>
class Base
{
public:
    void interface()
    {
        static_cast<T*>(this)->implementation();
    }
};

class Derived : public Base<Derived>
{
public:
    void implementation()
    {
        std::cout << "Derived implementation\n";
    }
};

int main()
{
    Derived d;
    d.interface();
}

d.interface()
Base<Derived>::interface()
static_cast<Derived*>(this)->implementation()
Derived::implementation()
이 과정이 컴파일 타임에 결정된다.

기존 상속은

  • virtual table 사용
  • runtime dispatch
  • 약간의 성능 비용

vtable을 사용하지 않고 다형성이 구현되고 컴파일 타임에 dispatch 하므로
런타임이 빨라진다.
하지만 template의 단점을 그대로 가져간다.

// Unreal Engine CRTP
struct FSharedFromThisBase  // 기반 클래스

// 기반 클래스로 만들어진 template class
template<class ObjectType, ESPMode Mode>  
class TSharedFromThis : private UE::Core::Private::FSharedFromThisBase

// template class를 상속받아 TSharedFromThis의 함수를 사용
struct FGraphPanelPinFactory : public TSharedFromThis<FGraphPanelPinFactory>

instruction cache(I-cache)

Instruction cache(I-cache)는 CPU가 실행할 “명령어 코드”를 저장하는 고속 캐시 메모리이다.
명령어는 원래 RAM에 있는데 RAM은 느리니까 CPU가 명령어를 미리 L1 I-cache에 저장해 둔다.
모든 코드는 명령어 체계이다.
int a = b + c;는 어셈블리로 치환되고

mov eax, b
add eax, c
mov a, eax

치횐된 어셈블리 명령어를 CPU가 하나씩 I-cache에서 가져와 실행한다.


컴파일 타임에 모든걸 결정하면 런타임 최적화가 될까?

프로그램이라는 모든게 정적이고 컴파일 타임에 확정될 순 없지만.
template을 써서 어떻게 만들었다고 가정해보자.
이미 빌드가 끝나고 나온 프로그램 상태에서는 컴파일 타임을 고려하지 않아도 된다.
이런 상황이면 런타임에 이루어질 연산을 전부
컴파일 타임에 옮겼으니 그만큼의 최적화가 발생할까?

결과는 아니다.

우선 template은 대입할 수 있는 모든 타입에 대한 코드를 만든다.
template을 사용하면 사용하는 타입 만큼 코드가 커진다.
커진 코드에서 I-cache가 miss될 확률이 높다.
그러면 CPU는 RAM에서 다시 명령어를 가져와야한다.

정리하면 template을 통해 만들어진 코드의 양이 많아지면
I-cache miss가 발생할 확률이 높아지고
I-cache miss가 발생하면 RAM으로 부터 명령어를 가져오는 cycle이 발생하므로
성능저하가 발생한다.

  1. 타입이 컴파일 타임에 결정되는가
  2. 타입 수가 많은가
  3. 코드가 작은가
  4. 런타임 데이터인가

를 고려해 virtual을 사용할 지 template을 사용할지 구분해야한다.


문제

객관식 10문제
https://gemini.google.com/share/078521c5e774

서술형 9문제

  1. C++ template이 무엇인지 설명해 주세요.

  2. template이 일반 함수 오버로드와 비교했을 때 어떤 장점이 있는지 설명해 주세요.

  3. 왜 template 정의를 보통 header에 두는지 설명해보세요.

  4. template specialization이 무엇인지 설명하고,

완전 특수화와 부분 특수화의 차이를 설명해 주세요.

  1. Template Meta Programming이 무엇인지 설명하세요.

  2. template을 많이 사용하면 단점은 무엇인지 설명해보세요.
    그럼에도 template을 사용하는 이유

  3. CRTP 패턴이 무엇인지 설명하고,
    virtual 함수를 사용하는 동적 다형성과 비교했을 때

정적 다형성(Static Polymorphism)으로서 가지는 성능적 이점은 무엇인가요?

내용에 대한 질의나, 수정 요청은 저에게 큰 도움이 됩니다.

'C\C++' 카테고리의 다른 글

Scope  (0) 2026.04.01
Object  (0) 2026.04.01
표현식  (0) 2026.04.01
포인터  (0) 2026.04.01
Virtual  (0) 2026.02.03