Template Type Deduction

개념

함수 템플릿에서 인자의 타입을 자동으로 추론하는 기능으로, T, T&, T&&는 각각 다른 규칙을 따른다.

type deduction 규칙 (타입 추론 규칙)

  • 함수 인자의 모양에 따라 3가지 규칙이 존재한다.
형태규칙
T함수 인자가 가진 const, volatile, reference 속성을 제거하고 T 타입을 결정한다.
T&함수 인자가 가진 reference 속성만 제거하고 T 타입을 결정한다. const, volatile 은 유지한다.
T&&인자가 3 (rvalue) 라면 T = int, T&& = int&&
인자가 n (lvalue) 라면 T = int&, T&& = int&

auto type deduction (auto 타입 추론)

  • template type deduction 규칙과 동일하다.
예시설명
T arg = 함수인자함수의 인자를 보고 T의 타입 결정
auto a = 우변우변을 보고 auto의 타입 결정

예제 코드

  int main()
{
    const int c = 10;
    fn(c);  // 함수 호출

    auto a1 = c;      // auto = int
    auto& a2 = c;     // auto = const int
                      // a2 = const int&

    int n = 10;
    auto&& a3 = 3;    // auto = int, a3 = int&&
    auto&& a4 = n;    // auto = int&, a4 = int&
}
  

Argument Decay (배열 전달 시 타입 변환)

배열을 함수에 전달할 때 포인터로 변환(decay) 되는 현상

예제 코드

  template<typename T> void f1(T arg)   // T = int*
{
    std::cout << __FUNCTION__ << std::endl;
}

template<typename T> void f2(T& arg)  // T = int[3], arg = int(&)[3]
{
    std::cout << __FUNCTION__ << std::endl;
}

int main()
{
    int x[3] = {1, 2, 3};
    
    f1(x);  // T = int*, arg = int*
    f2(x);  // T = int[3], arg = int(&)[3]

    auto a1 = x;   // auto = int*, int* a1 = x;
    auto& a2 = x;  // auto = int[3], a2 = int(&)[3]
}
  

int x[3]일 때, x의 정확한 타입은 int[3] 그러므로 아래와 같이 파악됨

코드 예시타입 해석 / 설명결과
auto a1 = xint a1[3] = xcompile error
auto a1 = xint* a1 = xcompile ok
auto& a2 = x;int(&a2)[3] = x;compile ok

Argument Decay 설명

  • 배열을 함수에 전달하면 포인터로 변환(decay) 됨.
함수 형태타입 추론 결과설명
f1(T arg)T = int*
f1(int* arg)
배열이 포인터로 decay됨
f2(T& arg)T = int[3]
arg = int(&)[3]
f2(int(&arg)[3])
참조로 받아서 decay되지 않음

Class Template Argument Deduction Guide

C++17 이전

  • 함수 템플릿은 타입 추론이 가능했지만, 클래스 템플릿은 타입 추론이 불가능.

C++17 이후

  • 클래스 템플릿도 생성자 인자를 통해 타입 추론 가능.

예시 코드: PAIR

  template<typename T, typename U> struct PAIR {
    T first;
    U second;
    PAIR() = default;
    PAIR(const T& a, const U& b): first(a), second(b) {}
};

int main() {
    PAIR<int, double> p1(3, 3.4);
    PAIR p2(3, 3.4); // C++17: 타입 추론
}
  

Deduction Guide 작성 예시: List

  #include <vector>

template<typename T> class List {
public:
    List() = default;
    List(std::initializer_list<T> e) {}
    template<typename C> List(const C& c) {}
    template<typename IT> List(IT first, IT last) {}
};

// Deduction Guide
template<typename C>
List(const C&) -> List<typename C::value_type>;

template<typename IT>
List(IT, IT) -> List<typename IT::value_type>;

int main() {
    std::vector<int> v = {1,2,3,4};
    List s1 = {1,2,3,4};
    List s2(v);
    List s3(v.begin(), v.end());
}
  
  • Deduction Guide는 iterator만으로도 타입을 추론할 수 있게 도와줌.

참고 링크

Object Generator와 Deduction

상황

  • Object Generator

    • 파괴될 때 등록된 함수를 호출해달라
      #include <iostream>
    void foo() { std::cout << "foo" << std::endl; }
    template<typename T> class scope_exit
    {
        T func;
    public:
        scope_exit(const T& f) : func(f) {}
        ~scope_exit() { func(); }
    };
    
    int main()
    {
        // scope_exit ce1(&foo); // cpp17
        // scope_exit<void(*)()> ce1(&foo);
        scope_exit<?> ce1([](){} );
    }
      
  • 개선

    • cpp14까지의 환경에서 void(*)()이런 식으로 쓰는 게 복잡하다
    • C++17 이전에는 클래스 템플릿은 타입 추론이 불가능했지만, 함수 템플릿을 통해 타입을 유추가능하다는 것을 활용
      #include <iostream>
    
    void foo() { std::cout << "foo" << std::endl; }
    
    template<typename T> class scope_exit
    {
        T func;
    public:
        scope_exit(const T& f) : func(f) {}
        ~scope_exit() { func(); }
    };
    
    template<typename T>
    scope_exit<T> make_scope_exit(const T& f)
    {
        return scope_exit<T>(f);
    }
    
    int main()
    {
        // scope_exit<void(*)()> ce1(&foo);
        // auto ce1 = make_scope_exit<void(*)()>(&foo);
        auto ce1 = make_scope_exit(&foo);
    
        std::cout << "----" << std::endl;
    }
      

실 사례

  #include <iostream>
#include <tuple>

template<typename T> void fn(const T& a) {}
int main()
{
    fn( std::pair<int, double>(3, 3.4) );
    fn( std::make_pair<int, double>(3, 3.4) );
    fn( std::make_pair(3, 3.4) );
    fn( std::tuple<int, double, int, char>(3, 3.4, 4, 'A') );
    fn( std::make_tuple(3, 3.4, 4, 'A') );
    // fn( std::tuple(3, 3.4, 4, 'A') ); // C++17
}