템플릿 함수 및 클래스와 C++11/14 혹은 그 이상의 버전에서 사용되는 auto 키워드에서 추론되는 형식 연역을 정리한다.
템플릿 형식 연역
템플릿 선언
template<typename T> // T에 대한 형식 연역
void f(ParamType param); // 전달된 param에 대한 형식 ParamType
호출 방식
f(expr); // expr 이라는 표현식을 사용
경우 1. ParamType이 포인터 혹은 참조 형식인 경우
단순 참조 형식 T&
template<typename T>
void f(T& param);
변수 선언 | 함수 호출 | 연역된 T 형식 | param의 형식 |
int x = 27; | f(x); | int | int & |
const int cx = x; | f(cx); | const int | const int & |
const int& rx = x; | f(rx); | const int | const int & |
- 특징
함수의 매개변수에서 "ParamType = T&" 이기 때문에,
rx 변수와 같은 const int& 기존 참조 형식에서 참조 형식&을 무시하고 T = const int로 연역된다.
T에 대한 패턴 매칭 방식
상수 참조 형식 const T&
template<typename T>
void f(const T& param);
변수 선언 | 함수 호출 | 연역된 T 형식 | param의 형식 |
int x = 27; | f(x); | int | const int & |
const int cx = x; | f(cx); | int | const int & |
const int& rx = x; | f(rx); | int | const int & |
포인터 T*
template<typename T>
void f(T* param);
변수 선언 | 함수 호출 | 연역된 T 형식 | param의 형식 |
int x = 27; | f(&x); | int | int * |
int* cx = &x; | f(cx); | int | int * |
const int* rx = cx; | f(rx); | const int | const int* |
포인터에 대한 연역도 참조 형식과 같다.
- 특징
T에 대한 패턴 매칭 방식으로 인해 T를 연역시 참조 형식도 무시되고 const 형식도 무시된다.
경우 2. ParamType이 보편 참조인 경우
보편 참조란 모든 ParamType에 대해 보편적으로 참조형으로 만든다는 명칭이다.
template<typename T>
void f(T&& param);
변수 선언 | 함수 호출 | 연역된 T 형식 | param의 형식 |
int x = 27; | f(x); | int & | int & |
const int cx = x; | f(cx); | const int & | const int & |
const int& rx = x; | f(rx); | const int & | const int & |
f(27); | int | int && |
이름 있는 변수, lvalue(x, cx, rx)는 모두 참조 형식으로 연역된다.
rvalue는 패턴 매칭이 적용되고 T는 && 형식을 제외되어 연역된다.
보편 참조에서 왼값에는 패턴 매칭이 적용되지 않는다.
경우 3. ParamType이 포인터도 아니고 참조도 아님
pass-by-value로 함수 매개변수가 전달된 상황이다.
template<typename T>
void f(T param);
변수 선언 | 함수 호출 | 연역된 T 형식 | param의 형식 |
int x = 27; | f(x); | int | int |
const int cx = x; | f(cx); | int | int |
const int& rx = x; | f(rx); | int | int |
const char* const str = "name"; | f(str); | const char* | const char* |
참조 형식일 경우 참조 형식은 무시, const 도 무시된다.
단순 값이 복사되어 전달되기 때문에 새로운 객체로 인식한다.
상수 값을 가리키는 상수 포인터의 경우 상수 값은 유지되고 포인터에 대한 상수성만 잃게된다.
배열 인수 / 함수 인수
포인터로 붕괴하는 현상
값 전달 방식의 템플릿 인수
template<typename T>
void f(T param);
char name[10] = "메뚜기";
f(name); // name은 char [10]의 배열이지만 T = char* 포인터로 연역(붕괴)된다.
void Foo(int, double);
f(Foo); // T는 void (*)(int double) 함수 포인터로 연역된다.
참조 형식의 템플릿 인수
template<typename T>
void f(T& param);
char name[10] = "메뚜기";
f(name); // T는 여기서 char [10]로 연역되고 param 타입은 char (&)[10] 이다.
참조 형식의 경우 배열은 실제 형식으로 연역된다.
void Foo(int, double);
f(Foo); // T는 void (&)(int double) 함수 참조로 연역된다.
auto 형식 연역
기본적으로 템플릿 형식 연역과 같다.
경우 1. 형식 지정자가 포인터나 참조 형식 인 경우 (보편 참조 x)
경우 2. 형식 지정자가 보편 참조 인 경우
경우 3. 형식 지정자가 포인터도 아니고 참조도 아닌 경우
auto x = 12; // int (경우 3)
const auto cx = x; // int (경우 3)
const auto& rx = x; // int (경우 1)
/* 경우 2 */
auto&& uref1 = x; // x는 lvalue, uref1 형식은 int&
auto&& uref2 = cx; // cx는 lvalue, uref2 형식은 const int&
auto&& uref3 = 27; // 27은 rvalue, uref3 형식은 int
포인터 붕괴외 참조 형식
const char name[] = "Arck"; // const char [5]
auto arr1 = name // arr1는 const char* 포인터
auto& arr2 = name // arr2는 const char (&)[5] 참조 형식의 배열
void Foo(int, double); // void(int, double) 형식의 함수
auto func1 = Foo; // func1는 void(*)(int, double) 함수 포인터
auto& func2 = Foo; // func2는 void(&)(int, double) 함수 참조
차이점 : std::initializer_list<T>
중괄호를 사용한 초기화의 경우 auto의 연역 방식에 std::initializer_list<T>가 추가된다.
auto x(10); // x는 int
auto x2 = {10}; // x는 std::initializer_list<int>
auto arr = {10, 20, 30, 40.0}; // 중괄호안의 변수 형식이 하나라도 다른 경우 연역 불가 (컴파일 에러)
템플릿 인수로 std::initializer_list<T>형식의 매개변수를 전달할 경우 연역에 실패한다.
template<typename T>
void func(T param);
func({1,2,3,4}); // 연역 실패
따라서 다음과 같이 std::initializer_list<T>를 인수로 명시해야 연역 가능하다.
template<typename T>
void func(std::initializer_list<T> param);
func({1,2,3,4}); // 연역 성공, T = int (패턴 매칭)
- C++14의 auto 키워드를 반환 형식으로 사용
기본적으로 함수 반환의 auto는 템플릿 연역 방식을 사용한다.
auto createInitList()
{
return {1,2,3,4}; // 연역 실패
}
auto createInitList()
{
return {1,2,3,4}; // 연역 실패
}
람다 함수내의 매개변수에 auto를 지정하는 경우도 마찬가지이다.
vector<int> vec;
const auto change_key = [&vec](const auto& new_arr) { vec = new_arr; }
change_key({5,4,3,2,1}); // 연역 실패
vector<int> vec;
const auto change_key = [&vec](const auto& new_arr) { vec = new_arr; };
change_key({5,4,3,2,1}); // 연역 실패
decltype
변수의 실제 형식을 그대로 말해준다.
int x = 10;
decltype(x) y = 2; // x는 int
vector<float> v = {1.2f, 3.2f};
decltype(v) f = {3.0f}; // f는 vector<float>
사용 예시
- 매개변수 타입을 따르는 반환 형식을 가진 함수 정의
template<typename Container, typename Index>
auto AuthorizeAccess(Container& c, Index i)
-> decltype(c[i])
{
authenticateUser();
return c[i];
}
C++ 11의 후행 반환 형식 ->은 이 뒤에 나올 형식이 실제 반환 타입을 따르는 것을 명시한다.
c와 i가 먼저 선언되어야 하기 때문에 후행에 선언된다.
만약 decltype을 사용하지 않는 다면 auto는 템플릿 연역법을 사용하므로 다음 예제에서 다음과 같이 연역되므로
auto = int가 되어 실제 c[i]의 타입 int& 참조가 아니여서 값이 변경되지 않는다.
template<typename Container, typename Index>
auto AuthorizeAccess(Container& c, Index i)
{
authenticateUser();
return c[i];
}
vector<int> data = {2,4,2,1,3};
AuthorizeAccess(data, 10) = 7; // 값 변경되지 않음
- C++ 14 형식의 정의
template<typename Container, typename Index>
decltype(auto) AuthorizeAccess(Container& c, Index i)
{
authenticateUser();
return c[i];
}
decltype(auto)를 사용하면 실제 반환값과 같은 형식으로 연역된다.
- decltype(x)는 decltype((x))와 다르다.
int x = 10;
decltype(x) = int;
decltype((x)) = int&;
연역된 형식을 파악하는 방법 typeinfo
decltype(x)는 함수가 아니기 때문에 사용자가 실제로 어떤 형식을 가리키는 지 알기가 어렵다.
typeinfo(x)는 x 변수에 대해 타입 정보를 반환하고
typeinfo(x).name()을 이용하면 형식에 대한 문자열을 반환하여 이 내용을 파악할 수 있다.
typeinfo는 함수이기 때문에 매개변수가 pass-by-value로 전달되어 템플릿 함수 내에서 T와 param 타입이 같게 나오는 경우가 있다.
template<typename T>
void f(const T& param)
{
using std::cout;
// T를 표시
cout << "T = "
<< typeinfo(T).name() // class const Widget *
<< "\n";
// param를 표시
cout << "param = "
<< typeinfo(param).name() // class const Widget *
<< "\n";
}
int main()
{
Widget inst = Widget;
f(&inst);
return 0;
}
const Widget * const & 타입 param 변수가 typeinfo(ptr) 내부에서 const Widget * ptr로 변형됨.
Boost의 type_index 라이브러리
boost::typeindex::type_id_with_cvr를 이용하여 정확한 템플릿 인자 T에 대한 연역정보를 얻을 수 있다.
#include <boost/type_index.hpp>
template<typename T>
void f(const T& param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
// T를 표시
cout << "T = "
<< type_id_with_cvr<T>().pretty_name() // T = class Widget const*
<< "\n";
// param를 표시
cout << "param = "
<< type_id_with_cvr<decltype(param)>().pretty_name() // param = class Widget const* const&
<< "\n";
}
int main()
{
Widget wgt = Widget;
f(&wgt);
}
참고: Effective Modern C++
'Advanced C++' 카테고리의 다른 글
[C++ 클래스] 템플릿 클래스와 전방 선언 (0) | 2021.03.24 |
---|---|
[Modern C++] (2) auto 키워드 (0) | 2021.03.22 |
[C++] 스마트 포인터 shared_ptr/unique_ptr/weak_ptr (0) | 2021.03.06 |
[C++] 함수 객체 Functor, std::function, std::bind (0) | 2021.02.22 |
[C++] 가변 길이 템플릿 Variadic Template (0) | 2021.02.21 |