Post List

2015년 2월 1일 일요일

item 01: template 타입 추론을 알아보자.

작가
Meyers, Scott
출판
O'ReillyMedia
발매
2014.12.12
평점








Effective Modern C++ - Scott Meyers
item 1. Understand template type deduction

item 1. template 타입 추론을 알아보자.

* Intro

C++98 에는 타입추론 (type deduction) 규칙이 하나 밖에 없었다. function template에서만 사용되었다. 하지만 거기에 대해서 자세히 알지 못해도, 사는데 큰 지장이 없었다. 하지만 C++ 11 이후에 type 추론이 2가지가 더 생겼다. auto 와 decltype에서 type 추론을 사용한다.

 여기에 Good news 와 Bad news가 있다.
- Good news는 auto의 type deduction은 C++98의 template type deduction을 바탕으로 하고 있다는 점이다.
- Bad news는 그동안 몰라도 행복할수 있었던 template type deduction에 대해서 이제는 이해할 필요가 생겼다.

먼저 앞으로 설명할 내용에 대해서 용어를 정의해보자.

template<typename T>       // T  template에서의 type 의미
void f(ParamType param);   // ParamType T 표현되는 함수정의에 적힌 param type

f(expr);                   // expr 함수 호출시 사용되는 표현

위 예제에서 Compiler는 T와 ParamType의 type deduction을 위해 expr를 사용한다.
그중 T의 type은 expr뿐만 아니라 ParamType의 영향도 받는다.

* 3 Cases for template type deduction

template의 type deduction 방법에는 3가지 경우에 대해서 생각해 보면 된다.

Case 1 : ParamType이 Reference (&) 이거나 Pointer (*) 인 경우. 단 Universal Reference (&&)는 제외

1. expr이 reference (&)면, expr의 reference (&)를 무시
2. 그런 다음 expr의 type과 ParamType을 pattern-match하여 T의 type을 판단한다.

위 2가지 규칙만 적용하면 쉽긴한데, 예제를 몇개 보면 좀 더 명확해 질 것이다.

먼저 ParamType이 T & 인 경우에 대해서 한 번 보자.

template <typename T>
f(T & param);         // ParamType : T &

int x = 27;           // expr : int
const int cx = x;     // expr : const int
const int & rx = x;   // expr : const int&

f(x);      // T : int ,      ParamType : int &
f(cx);     // T : const int, ParamType : const int&
f(rx);     // T : const int, ParamType : const int&



rx에 대해서 1번 규칙이 적용되어 expr&를 무시하고 전달하였다. 2번 규칙에 대해서는 여기서 적용된 case가 없다.
아래 예제는 ParamType이 T & 인 경우에 대해서 한 번 보자.

template <typename T>
f(const T& param);     // ParamType : const T &

int x = 27;            // expr : int
const int cx = x;      // expr : const int
const int & rx = x;    // expr : const int&

f(x);      // T : int, ParamType : const int&
f(cx);     // T : int, ParamType : const int&
f(rx);     // T : int, ParamType : const int&



cx 와 rx의 경우 expr에 붙은 const가 ParamType과의 pattern-match 과정에서 const int가 되어버려서 T의 const가 사라지게 되었다. (그렇다고 떡하니 ParamType에 const라고 대놓고 선언했는데, 저것을 없앨수는 없잖아.)

다음으로는 Pointer (*)의 경우에 대해서 한번 보자.

template <typename T>
f(T * param);             // ParamType : T *

int x = 27;               // expr : int -> int*
const int* px = &x;       // expr : const int*

f(&x);     // T : int      , ParamType : int*
f(px);     // T : const int, ParamType : const int*



뭐, 자세한 설명은 생략하겠다.

Case 2 : ParamType이 Universal Reference (&&)인 경우

1. expr이 lvalue 인 경우, T와 ParamType을 모두 lvalue reference (&)로 추론하면 된다.
   (쉽게 말해서 ParamType&를 하나 빼고, expr은 전부다 &를 붙여주고 그냥 Case 1 규칙 적용하면 끝)
2. expr이 rvalue 인 경우, 그냥 Case 1과 같이 적용하면 된다.

template <typename T>
f(T&& param);         // ParamType : T&& (Universal reference)

int x = 27;
const int cx = x;
const int & rx = x;

f(x);  // lvalue => T : int &       , ParamType : int&
f(cx); // lvalue => T : const int & , ParamType : const int&
f(rx); // lvalue => T : const int & , ParamType : const int&
f(27); // rvalue => T : int         , ParamType : int&&



위의 3가지는 경우는 ParamType&를 하나 빼주고, expr은 전부다 &가 1개 있는걸로 간주하고 생각하면 되고, 아래 27이라는 rvalue를 넣어줬을 때는 그냥 있는 그대로 해석하면 된다.

Case 3 : ParamType이 그냥 Call-by-Value인 경우

- reference (&), volatileconst 모두 다 무시한다.

어차피 expr의 값을 복사해서 사용하는 것이니 원래 전달되기 전의 값이 어떤 특성을 가졌는지는 아무런 의미가 없다. 그냥 복사해서 함수안에서 구워먹든 삶아먹든 다 허용된다.
단, 문자열을 저장한 const charconst str 같은걸 전달할때는 조금 생각을 해봐야 하는데, pointer 그 자체의 const만 무시한다. 즉, 문자열값 자체는 바꿀수가 없지만, 해당 pointer가 다른것을 가리키도록 변경 할 수는 있다.

template <typename T>
f(T param);

const charconst str = "Luna the Star";

f(str);        // T : const char* , ParamType : const char*



Array Arguments

C++에서 배열값을 인자로 넘길때는 pointer로 처리된다.
ParamType이 T인 경우 expr로 배열을 넘기면 pointer로 넘어가게 되는데,
ParamType이 &인 경우는 pointer가 아닌 배열로 추론된다.

template <typename T>
f(T param);            // ParamType : T

template <typename T>
fr(T & param);         // ParamType : T&

const char str[] = "Luna the Star";
const char* pStr = str;

f(str);        //   T : const char*
f(pStr);

fr(str);       // T : const char (&)[]
fr(pStr);      // T : const char*



그래서 ParamType이 &인 경우에는 배열의 크기 값을 알 수가 있다.

template<typename T, std::size_t N>
constexpr std::size_t arraySize(T(&)[N]) noexcept
{
        return N;
}

int keyValues[] = { 1,2,3,4,5,6,7 };

std::cout << arraySize(keyValues) << std::endl;


Function Arguments

function pointer의 경우 ParamType이 T 이면 param이 pointer (*)로 처리되고, & 이면 param이 reference (&)로 처리된다.

void someFunc(intdouble); //void(int, double)

template<typename T>
void f1(T param);

template<typename T>
void f2(T& param);

f1(someFunc); // ptr to func : void (*)(int, double)
f2(someFunc); // ref to func : void (&)(int, double)



Things to Remember

template type deduction시 reference (&)인 인자의 &는 무시된다.
* Universal reference (&&) 인자를 type deduction 할때 lvalue인 경우는 특별하게 처리한다. (TParamType 둘다 &가 있는 걸로 처리)
* call-by-value 인자의 type deduction 할때는 constvolatile등의 키워드는 모두 무시된다.
* 배열, 함수가 인자로 type deduction 할때는 decay to pointer 규칙에 의해 pointer로 처리된다. 단 ParamType에 &가 붙은 경우는 원래 type 그대로 참조된다.