Scribbles Help

[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'이 있다는 것을 기억해두는게 좋겠다.

참고

  • std::decay: 좀더 세부적인 내용에 대해서 확인하도록 할 것. implicit conversion 관련 내용의 링크도 내용 파악하는 것이 좋을 것임.

  • 사용된 예제 코드

Last modified: 03 January 2024