On this page
article
C++ Template type deduction
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 = x | int a1[3] = x | compile error |
auto a1 = x | int* a1 = x | compile 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
}