[C++11] std::decay
decay는 사전적으로 '부식'을 의미한다. C++에서 decay란 특정 컨텍스트(context)에서 타입(type)이 원래 고유의 것이 아닌 다른 타입으로 변질되는 것을 말한다. std::decay는 decay가 일어나는 컨텍스트에서의 타입을 계산해주는 metafunction이다. 즉 std::decay는 다음과 같이 값(value) 인자를 받는 함수 템플릿이 있을때 함수 호출시 template type deduction rule로 추론되는 T 타입 값을 얻을 수 있게 해준다는 것이다.
template <typename T>
T func(T t);
// ...
int i = 100;
int & ri = i;
int const j = 200;
int const volatile k = 300;
static_assert(is_same<int, decltype(func(i))>::value, "");
static_assert(is_same<int, decltype(func(ri))>::value, "");
static_assert(is_same<int, decltype(func(j))>::value, "");
static_assert(is_same<int, decltype(func(k))>::value, "");
예제 코드에서 보다시피 값으로 변수 인자를 받는 함수 템플릿의 T 타입에는 & 레퍼런스와 const, volatile qualifier가 제거되었다.
배열과 함수가 인자로 올 경우는 기본적으로 & 레퍼런스가 제거되고 추가적인 decay가 일어난다. 아래에서 각각에 대해 좀더 설명한다.
array-to-pointer decay
가장 대표적인 decay의 예가 array-to-pointer decay이다:
using std::is_same;
using std::decay; // C++11
using std::decay_t; // C++14
// ...
// int array to pointer decay
int a[3] = { 1, 2, 3 };
int * pa = a;
static_assert(is_same<decltype(a), int[3]>::value, "");
static_assert(is_same<decltype(pa), int *>::value, "");
static_assert(is_same<decltype(pa), decay<decltype(a)>::type>::value, "");
static_assert(is_same<decltype(pa), decay_t<decltype(a)>>::value, "");
array-to-pointer decay가 일어난 컨텍스트에서는 배열의 최상위 차원 크기를 알 수 없게 된다. 1차원 배열이 가장 많이 사용되게되는데, 포인터 변수만을 놓고 보면 해당 포인터가 가리키는 것이 단일 객체인지 배열인지 알 수 없다. 보통 배열과 배열크기정보를 같이 전달하여 처리하는 패턴으로 코드를 작성한다. 전달하는 크기정보가 잘못되었거나 범위를 넘어가는 인덱스 조작을 통해서 보통 문제가 발생하곤 한다.
다차원 배열에서의 decay 예는 다음과 같은 형태가 된다:
// multi-dimensional array to pointer decay
int aa[2][3] = {
{ 1, 2, 3 },
{ 4, 5, 6 }
};
int (* paa) [3] = aa; // loses top-level dimension size information, 2.
static_assert(is_same<decltype(aa), int [2][3]>::value, "");
static_assert(is_same<decltype(paa), int (*) [3]>::value, ""); // pointer to 'int [3]'
paa[1][0] = 44;
assert(aa[1][0] == 44);
string literal
문자열 리터럴은 1차원 배열이다. 따라서 당연히 decay가 일어난다.
// string literal is a lvalue.
// "abc"'s type is char const [4].
char const str[4] = "abc";
char const (* pStr) [4] = &"abc"; // can get the address of the string.
char const * pStr1 = "abc"; // array to pointer decay
char const * pStr2 = str; // array to pointer decay
//char const (* pStr3) [4] = str; // this is a compile error
char const (* pStr4) [4] = &str; // but this works OK.
static_assert(std::is_same<char const (&) [4], decltype("abc")>::value, "");
function-to-pointer decay
자주 사용하면서 당연하다고 생각하는 또다른 decay는 function-to-pointer decay이다. C++에는 function pointer type이외에 function type과 function reference type이 존재한다. function pointer type은 알아도 나머지 것들에 대해서 잘 생각하지 못하는 경우들이 있다. 함수는 int와 같은 primitive type처럼 또다른 함수의 인자로 전달할 수 있는 first class type이라서 &, * 등을 붙여서 compound type을 구성하는 것이 가능하다.
int f(int i) { return i + 1; }
// ...
auto pf = f;
int (&rf) (int) = f;
int (*pf1) (int) = f;
static_assert(is_same<int (int), decltype(f)>::value, ""); // function type
static_assert(is_same<int (&) (int), decltype(rf)>::value, ""); // function reference type
static_assert(is_same<int (*) (int), decltype(pf)>::value, ""); // function pointer type
static_assert(is_same<int (*) (int), decay<decltype(f)>::type>::value, "");
static_assert(is_same<int (*) (int), decay_t<decltype(f)>>::value, "");
Function template에 인자로 전달시 decay
template을 통한 generic한 코드를 작성시에는 이런 decay 상황에 대해서 대비해야한다. 오버로딩 레졸루션 규칙(overload resolution rule)에 의해서 원하지 않는 함수가 호출 되거나 컴파일이 실패하는 상황이 발생할 수 있다. 적절한 함수 오버로딩을 추가하거나 type trait과 std::enable_if와 같은 도구를 활용하여 오버로딩을 제어할 필요가 있을 수도 있겠다.
// template type deduction rule is applied for the parameter.
template <typename T>
auto ReturnDecayedValue(T t)
{
return t; // template type deduction rule(auto) is applied for the return type.
}
// template type deduction rule is applied for the parameter.
template <typename T>
auto ReturnDecayedValue1(T t) -> decltype(t)
{
return t; // decltype deduction rule is applied for the return type.
}
// template type deduction rule is applied for the parameter.
template <typename T>
decltype(auto) ReturnDecayedValue2(T t) // C++14 decltype(auto)
{
return t; // decltype deduction rule is applied for the return type.
}
template <typename T>
auto ReturnDecayedValue3(T & t)
{
return t; // template type deduction rule(auto) is applied for the return type.
}
template <typename T>
auto ReturnDecayedValue4(T & t) -> decltype(t)
{
return t; // decltype deduction rule is applied for the return type.
}
template <typename T>
decltype(auto) ReturnDecayedValue5(T & t) // C++14 decltype(auto)
{
return t; // decltype deduction rule is applied for the return type.
}
// ...
int f(int i) { return i + 1; }
// ...
int a[3] = { 1, 2, 3 };
// ...
int aa[2][3] = {
{ 1, 2, 3 },
{ 4, 5, 6 }
};
// ...
static_assert(is_same<int *, decltype(ReturnDecayedValue(a))>::value, "");
static_assert(is_same<int (*) [3], decltype(ReturnDecayedValue(aa))>::value, "");
static_assert(is_same<int (*) (int), decltype(ReturnDecayedValue(f))>::value, "");
static_assert(is_same<int *, decltype(ReturnDecayedValue1(a))>::value, "");
static_assert(is_same<int (*) [3], decltype(ReturnDecayedValue1(aa))>::value, "");
static_assert(is_same<int (*) (int), decltype(ReturnDecayedValue1(f))>::value, "");
static_assert(is_same<int *, decltype(ReturnDecayedValue2(a))>::value, "");
static_assert(is_same<int (*) [3], decltype(ReturnDecayedValue2(aa))>::value, "");
static_assert(is_same<int (*) (int), decltype(ReturnDecayedValue2(f))>::value, "");
//
static_assert(is_same<int *, decltype(ReturnDecayedValue1(a))>::value, "");
static_assert(is_same<int (*) [3], decltype(ReturnDecayedValue1(aa))>::value, "");
static_assert(is_same<int (*) (int), decltype(ReturnDecayedValue1(f))>::value, "");
static_assert(is_same<int (&) [3], decltype(ReturnDecayedValue4(a))>::value, ""); // reference to 'int [3]'
static_assert(is_same<int (&) [2][3], decltype(ReturnDecayedValue4(aa))>::value, ""); // reference to 'int [2][3]'
static_assert(is_same<int (&) (int), decltype(ReturnDecayedValue4(f))>::value, ""); // reference to 'int (int)'
static_assert(is_same<int (&) [3], decltype(ReturnDecayedValue5(a))>::value, ""); // reference to 'int [3]'
static_assert(is_same<int (&) [2][3], decltype(ReturnDecayedValue5(aa))>::value, ""); // reference to 'int [2][3]'
static_assert(is_same<int (&) (int), decltype(ReturnDecayedValue5(f))>::value, ""); // reference to 'int (int)'
array에도 당연히 'array type, array reference type, array pointer type'이 있다는 것을 기억해두는게 좋겠다.
Last modified: 03 January 2024