C++ Template Basics

1. ν…œν”Œλ¦Ώ λ§€κ°œλ³€μˆ˜μ˜ μ’…λ₯˜: νƒ€μž… vs. λΉ„-νƒ€μž…

C++ ν…œν”Œλ¦Ώμ€ 두 κ°€μ§€ μ£Όμš” μ’…λ₯˜μ˜ λ§€κ°œλ³€μˆ˜λ₯Ό ν—ˆμš©ν•©λ‹ˆλ‹€.

μ’…λ₯˜ ν‚€μ›Œλ“œ μ „λ‹¬λ˜λŠ” 것 μ‚¬μš© μ˜ˆμ‹œ
νƒ€μž… λ§€κ°œλ³€μˆ˜ (Type Parameter) typename λ˜λŠ” class μž„μ˜μ˜ 데이터 νƒ€μž… (int, std::string, μ‚¬μš©μž μ •μ˜ 클래슀 λ“±) template <typename T>
λΉ„-νƒ€μž… λ§€κ°œλ³€μˆ˜ (Non-Type Parameter) κΈ°λ³Έ νƒ€μž… (int, bool, size_t λ“±) 컴파일 μ‹œκ°„μ— μ•Œλ €μ§„ κ°’ (μƒμˆ˜) template <int N>

2. ν…œν”Œλ¦Ώ λ§€κ°œλ³€μˆ˜λ₯Ό μ„ μ–Έν•˜λŠ” λ‹€μ–‘ν•œ 방법

ν…œν”Œλ¦Ώ λ§€κ°œλ³€μˆ˜λ₯Ό μ„ μ–Έν•  λ•ŒλŠ” 이름(Identifier)κ³Ό κΈ°λ³Έκ°’(Default Value)의 μœ λ¬΄μ— 따라 λ‹€μ„― κ°€μ§€ μ£Όμš” νŒ¨ν„΄μ΄ λ‚˜νƒ€λ‚˜λ©°, 각각의 μ‚¬μš© λͺ©μ μ΄ λ‹€λ¦…λ‹ˆλ‹€.

2.1. λΉ„-νƒ€μž… ν…œν”Œλ¦Ώ λ§€κ°œλ³€μˆ˜ (Non-Type Template Parameters)

이 λ§€κ°œλ³€μˆ˜λ“€μ€ 컴파일 μ‹œκ°„μ— μ•Œλ €μ§„ κ°’(Value)을 ν…œν”Œλ¦Ώμ— μ „λ‹¬ν•©λ‹ˆλ‹€. (예: λ°°μ—΄ 크기, μƒμˆ˜ ν”Œλž˜κ·Έ λ“±)

template <int N>
class A { int v{N}; }; // N을 λ‚΄λΆ€μ—μ„œ μ‚¬μš©

template <int N = 10>
class B { int v{N}; }; // N을 λ‚΄λΆ€μ—μ„œ μ‚¬μš©, κΈ°λ³Έκ°’ 제곡

template <int = 10>
class C { int v{0}; }; // 이름 μ—†κ³ , κΈ°λ³Έκ°’λ§Œ 제곡 (SFINAE 용)

template <int>
class D { int v{0}; }; // 이름도 기본값도 μ—†μŒ (ν˜•μ‹ μΆ©μ‘± 용)

template <int, int>
class E { int v{0}; }; // μ—¬λŸ¬ 개λ₯Ό λ‚˜μ—΄

int main()  
{  
    A<10> a{};  
    B<> b{};  
    C<> c{};  
    D<10> d{};  
    E<10, 10> e{};  
}
클래슀 μ„ μ–Έ (template <…>) νŠΉμ§• 및 μ—­ν•  ν™œμš© μ˜ˆμ‹œ
A template <int N> 이름 있음, κΈ°λ³Έκ°’ μ—†μŒ. κ°€μž₯ 일반적인 ν˜•νƒœμ΄λ©°, μ „λ‹¬λœ 값을 λ‚΄λΆ€ λ‘œμ§μ— μ‚¬μš©ν•©λ‹ˆλ‹€. std::array<int, 5>의 5와 같이 크기λ₯Ό 전달할 λ•Œ
B template <int N = 10> 이름 있음, κΈ°λ³Έκ°’ 있음. μΈμˆ˜κ°€ 없을 경우 기본값을 μ‚¬μš©ν•˜μ—¬ νŽΈμ˜μ„±μ„ λ†’μž…λ‹ˆλ‹€. μ˜΅μ…˜ ν”Œλž˜κ·Έκ°€ μžˆλŠ” ν…œν”Œλ¦Ώ
C template <int = 10> 이름 μ—†μŒ, κΈ°λ³Έκ°’ 있음. μ „λ‹¬λœ 값을 μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ©°, 인수λ₯Ό μƒλž΅ν•˜κΈ° μœ„ν•΄ 기본값을 μ œκ³΅ν•©λ‹ˆλ‹€. std::enable_if의 논리 μ œμ–΄ 게이트 역할을 μœ„ν•΄ μ‚¬μš©λ©λ‹ˆλ‹€. std::enable_ifλ₯Ό μ‚¬μš©ν•œ ν•¨μˆ˜ μ˜€λ²„λ‘œλ”©
D template <int> 이름 μ—†μŒ, κΈ°λ³Έκ°’ μ—†μŒ. μ „λ‹¬λœ 값을 μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ©°, 였직 ν˜•μ‹μ μœΌλ‘œ λ§€κ°œλ³€μˆ˜κ°€ ν•˜λ‚˜ ν•„μš”ν•¨μ„ λ‚˜νƒ€λƒ…λ‹ˆλ‹€. 거의 μ‚¬μš©λ˜μ§€ μ•ŠμŒ (Aκ°€ 더 λͺ…확함)

2.2. νƒ€μž… ν…œν”Œλ¦Ώ λ§€κ°œλ³€μˆ˜ (Type Template Parameters)

이 λ§€κ°œλ³€μˆ˜λ“€μ€ νƒ€μž…(Type)을 ν…œν”Œλ¦Ώμ— μ „λ‹¬ν•©λ‹ˆλ‹€. (예: int, float, μ‚¬μš©μž μ •μ˜ ꡬ쑰체 λ“±)

template <typename T>
class A { T v{0}; }; // Tλ₯Ό λ‚΄λΆ€μ—μ„œ μ‚¬μš©

template <typename T = int>
class B { T v{0}; }; // Tλ₯Ό λ‚΄λΆ€μ—μ„œ μ‚¬μš©, κΈ°λ³Έκ°’ 제곡

template <typename = int>
class C { int v{0}; }; // 이름 μ—†κ³ , κΈ°λ³Έκ°’λ§Œ 제곡 (SFINAE 용)

template <typename>
class D { int v{0}; }; // 이름도 기본값도 μ—†μŒ (ν˜•μ‹ μΆ©μ‘± 용)

template <typename, typename>
class E { int v{0}; }; // μ—¬λŸ¬ 개λ₯Ό λ‚˜μ—΄

int main()  
{  
    A<int> a{};  
    B<> b{};  
    C<> c{};  
    D<int> d{};  
    E<int, int> e{};  
}
클래슀 μ„ μ–Έ (template <…>) νŠΉμ§• 및 μ—­ν•  ν™œμš© μ˜ˆμ‹œ
A template <typename T> 이름 있음, κΈ°λ³Έκ°’ μ—†μŒ. κ°€μž₯ 일반적인 ν˜•νƒœμ΄λ©°, μ „λ‹¬λœ νƒ€μž…μ„ λ‚΄λΆ€μ—μ„œ μ‚¬μš©ν•©λ‹ˆλ‹€. std::vector<int>의 int와 같이 μ»¨ν…Œμ΄λ„ˆμ˜ νƒ€μž…μ„ μ •μ˜ν•  λ•Œ
B template <typename T = int> 이름 있음, κΈ°λ³Έκ°’ 있음. μΈμˆ˜κ°€ 없을 경우 κΈ°λ³Έ νƒ€μž…(int)을 μ‚¬μš©ν•˜μ—¬ νŽΈμ˜μ„±μ„ λ†’μž…λ‹ˆλ‹€. std::map의 κΈ°λ³Έ λΉ„κ΅μž(Compare = std::less<Key>)
C template <typename = int> 이름 μ—†μŒ, κΈ°λ³Έκ°’ 있음. μ „λ‹¬λœ νƒ€μž…μ„ μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ©°, 인수λ₯Ό μƒλž΅ν•˜κΈ° μœ„ν•΄ 기본값을 μ œκ³΅ν•©λ‹ˆλ‹€. std::enable_if의 핡심 νŒ¨ν„΄μž…λ‹ˆλ‹€. std::enable_ifλ₯Ό μ‚¬μš©ν•œ ν•¨μˆ˜ μ˜€λ²„λ‘œλ”©
D template <typename> 이름 μ—†μŒ, κΈ°λ³Έκ°’ μ—†μŒ. 였직 ν˜•μ‹μ μœΌλ‘œ νƒ€μž… λ§€κ°œλ³€μˆ˜κ°€ ν•˜λ‚˜ ν•„μš”ν•¨μ„ λ‚˜νƒ€λƒ…λ‹ˆλ‹€. 거의 μ‚¬μš©λ˜μ§€ μ•ŠμŒ

3. 핡심 νŒ¨ν„΄: std::enable_if와 이름 μ—†λŠ” λ§€κ°œλ³€μˆ˜ (Class C)

std::enable_ifλŠ” ν…œν”Œλ¦Ώμ˜ μΆ”κ°€ λ§€κ°œλ³€μˆ˜ μœ„μΉ˜μ— μ‚½μž…λ˜μ–΄, 쑰건에 따라 ν•΄λ‹Ή ν…œν”Œλ¦Ώμ„ 선택 ν›„λ³΄μ—μ„œ μ œμ™Έν•˜λŠ” λ°©μ‹μœΌλ‘œ λ™μž‘ν•©λ‹ˆλ‹€ (SFINAE).

3.1. νŒ¨ν„΄ 1: νƒ€μž… ν…œν”Œλ¦Ώ λ§€κ°œλ³€μˆ˜ μ‚¬μš© (Type Template Parameter)

이 방식은 좔가적인 νƒ€μž… λ§€κ°œλ³€μˆ˜λ₯Ό μ„ μ–Έν•˜κ³ , κ·Έ 기본값을 std::enable_if_t의 결과둜 μ„€μ •ν•©λ‹ˆλ‹€.

#include <type_traits> // std::is_integral, std::enable_if_t

// Tκ°€ μ •μˆ˜ν˜•μΌ λ•Œλ§Œ 이 ν•¨μˆ˜κ°€ μœ νš¨ν•¨
template <typename T, 
          typename = std::enable_if_t<std::is_integral<T>::value, float> // (A)
         >
void check_type_foo() 
{
    std::cout << "Pattern 1: TλŠ” μ •μˆ˜ν˜•μž…λ‹ˆλ‹€ (νƒ€μž… λ§€κ°œλ³€μˆ˜ μ‚¬μš©)" << std::endl;
}

// μ‚¬μš© μ˜ˆμ‹œ
check_type_foo<int>();   // (A)κ°€ float으둜 μΉ˜ν™˜λ˜μ–΄ 성곡
// check_type_foo<float>(); // (A)κ°€ μΉ˜ν™˜ μ‹€νŒ¨(SFINAE)ν•˜μ—¬ 컴파일 μ—λŸ¬

λ™μž‘ 원리 뢄석:

  • 성곡 μ‹œ (T = int): std::enable_if_t<true, float> $\to$ float νƒ€μž…μœΌλ‘œ μΉ˜ν™˜.

    • μ΅œμ’… ν…œν”Œλ¦Ώ 선언은 template <typename T, typename = float>이 λ˜μ–΄ μœ νš¨ν•œ Class C νƒ€μž… νŒ¨ν„΄μ΄ μ™„μ„±λ©λ‹ˆλ‹€.
  • μ‹€νŒ¨ μ‹œ (T = float): std::enable_if_t<false, float> $\to$ type 멀버λ₯Ό 찾을 수 μ—†μ–΄ μΉ˜ν™˜ μ‹€νŒ¨κ°€ λ°œμƒν•˜λ©°, 이 ν•¨μˆ˜λŠ” 컴파일러의 후보 λͺ©λ‘μ—μ„œ μ œμ™Έλ©λ‹ˆλ‹€.


3.2. νŒ¨ν„΄ 2: λΉ„-νƒ€μž… ν…œν”Œλ¦Ώ λ§€κ°œλ³€μˆ˜ μ‚¬μš© (Non-Type Template Parameter)

이 방식은 좔가적인 λΉ„-νƒ€μž… λ§€κ°œλ³€μˆ˜λ₯Ό μ„ μ–Έν•˜κ³ , κ·Έ νƒ€μž…μ„ std::enable_if_t의 결과둜 μ„€μ •ν•˜λ©° κΈ°λ³Έ κ°’(= true)을 ν• λ‹Ήν•©λ‹ˆλ‹€

#include <type_traits> // std::is_integral, std::enable_if_t

// Tκ°€ μ •μˆ˜ν˜•μΌ λ•Œλ§Œ 이 ν•¨μˆ˜κ°€ μœ νš¨ν•¨
template <typename T,
          std::enable_if_t<std::is_integral<T>::value, bool> = true // (B)
         >
void check_type_bar() 
{
    std::cout << "Pattern 2: TλŠ” μ •μˆ˜ν˜•μž…λ‹ˆλ‹€ (λΉ„-νƒ€μž… λ§€κ°œλ³€μˆ˜ μ‚¬μš©)" << std::endl;
}

// μ‚¬μš© μ˜ˆμ‹œ
check_type_bar<long>();   // (B)κ°€ bool = true둜 μΉ˜ν™˜λ˜μ–΄ 성곡
// check_type_bar<double>(); // (B)κ°€ μΉ˜ν™˜ μ‹€νŒ¨(SFINAE)ν•˜μ—¬ 컴파일 μ—λŸ¬

λ™μž‘ 원리 뢄석:

  • 성곡 μ‹œ (T = long): std::enable_if_t<true, bool> $\to$ bool νƒ€μž…μœΌλ‘œ μΉ˜ν™˜.

    • μ΅œμ’… ν…œν”Œλ¦Ώ 선언은 template <typename T, bool = true>이 되며, μ΄λŠ” μœ νš¨ν•œ Class C λΉ„-νƒ€μž… νŒ¨ν„΄μ΄ μ™„μ„±λ©λ‹ˆλ‹€.
  • μ‹€νŒ¨ μ‹œ (T = double): std::enable_if_t<false, bool> $\to$ type 멀버λ₯Ό 찾을 수 μ—†μ–΄ μΉ˜ν™˜ μ‹€νŒ¨κ°€ λ°œμƒν•˜λ©°, 이 ν•¨μˆ˜λŠ” 후보 λͺ©λ‘μ—μ„œ μ œμ™Έλ©λ‹ˆλ‹€.

두 νŒ¨ν„΄μ˜ 차이점 및 μž₯점

νŠΉμ§• νŒ¨ν„΄ 1: νƒ€μž… λ§€κ°œλ³€μˆ˜ (typename = …) νŒ¨ν„΄ 2: λΉ„-νƒ€μž… λ§€κ°œλ³€μˆ˜ (Type = Value)
μ„ μ–Έ ν˜•νƒœ νƒ€μž… λ§€κ°œλ³€μˆ˜μ˜ 기본값을 μ‘°μž‘ λΉ„-νƒ€μž… λ§€κ°œλ³€μˆ˜μ˜ νƒ€μž…μ„ μ‘°μž‘
닀쀑 μ˜€λ²„λ‘œλ“œ λ™μΌν•œ νƒ€μž… μ‹œκ·Έλ‹ˆμ²˜λ‘œ κ°„μ£Όλ˜μ–΄ 컴파일 였λ₯˜ λ°œμƒ (ν•¨μˆ˜ fooλ₯Ό 두 번 μ„ μ–Έν•˜λŠ” 경우) μ„œλ‘œ λ‹€λ₯Έ λΉ„-νƒ€μž… λ§€κ°œλ³€μˆ˜ 기본값을 κ°€μ§ˆ 수 μžˆμ–΄ ν…œν”Œλ¦Ώ μ˜€λ²„λ‘œλ”©μ— 적합
가독성 λ‹¨μˆœν•¨ Type = Value ν˜•νƒœκ°€ μ•½κ°„ λ³΅μž‘ν•¨

Reference

https://leimao.github.io/blog/CPP-Enable-If/