-
[Effective Modern C++] Type deduction : autoC++/Effective Modern C++ 2021. 12. 27. 15:41
형식 연역 (Type deduction)은 소스코드에서 특정한 코드가 어떤 것으로 바뀌는 것 입니다.
여기서 '특정한 코드'란 template, auto, decltype 등 이 있으며
바뀌게 되는 '어떤 것' 에는 자료형부터, 함수, 람다식 등이 있습니다.
이전 글에서, template에 대한 형식 연역을 살펴보았습니다.
몇 가지 예외를 제외하면, 특정한 규칙에 따라 연역이 직관적으로 이루어짐을 볼 수 있었습니다.
auto는 template과 유사합니다. 한 가지 경우만 제외하면, template와 동일하게 연역됩니다.
이번 글에서는 auto의 형식 연역에 대하여 살펴보도록 하겠습니다.
함수 템플릿은 일반적으로 아래와 같이 표현되고, 호출됩니다.
//Declaration template<typename T> void function(ParamType param); //Call function(expr);
이 때, expr에 대하여 T와 ParamType이 각각 다르게 연역될 수 있었음을 보았습니다.
auto를 이용한 변수 선언에서 auto는 T와 동일하게, 그리고 형식 지정자 (Type specifier)가 ParamType과 동일하게 취급됩니다.
예제를 살펴보도록 하겠습니다.
auto x = 27; const auto cx = x; const auto& rx = x;
위와 같은 auto 선언에서, template의 T와 ParamType에 대응되는 요소는 아래 표와 같습니다.
Declaration T ParamType auto x auto auto const auto auto const auto const auto& auto const auto& 핵심은 변수가 어떤 형식으로 연역되는지 입니다.
1. 기본적인 형식 연역
더보기template의 형식 연역은 세 가지로 구분되어 나뉘게 됩니다.
Case 1. 형식 지정자가 포인터나 참조 형식이지만 보편 참조(Universal reference)는 아닌 경우
- 포인터나 참조를 무시한 후, ParamType에 대하여 Pattern-matching방식으로 대응시켜서 T의 형식을 결정합니다.
Case 2. 형식 지정자가 보편 참조인 경우
- expr이 l-value일 경우 T와 ParamType 모두 l-value로 연역됩니다.
- expr이 r-value일 경우 포인터, 참조 형식과 동일한 규칙이 적용됩니다.
Case 3. 형식 지정자가 포인터도 아니고 참조도 아닌 경우
- 참조, const, volatile등의 형식 지정자를 무시하여 연역합니다.
auto의 형식 연역 또한 위의 경우와 유사하게 됩니다.
예제를 보며 각각의 경우를 살펴보도록 하겠습니다.
auto x = 27; const auto cx = x; const auto& rx = x; auto&& uref1 = x; auto&& uref2 = cx; auto&& uref3 = 27;
위의 auto선언에서, 각각의 변수에 대응하는 경우와, 연역되는 형식은 아래와 같습니다.
Declaration Case Type auto x = 27 3 int const auto cx = x 3 int const auto& rx = x 1 const int auto&& uref = x 2 (x는 l-value) int& auto&& uref2 = cx 2 (cx는 l-value) const int& auto&& uref3 = 27 2 (27은 r-value) int&& 위의 세 가지 경우 외에도 특별한 경우가 있었습니다.
배열과 함수가 포인터로 연역되는 경우 입니다.
예제를 살펴보도록 하겠습니다.
const char arr[] = "Array"; auto arr1 = arr; auto& arr2 = arr; void function(int, double); auto func1 = function; auto& func2 = function;
위의 배열과 함수, 그리고 그것을 받는 각각의 auto에 대하여 연역되는 형식은 아래와 같습니다.
Declaration Type Declaration Type auto arr1 = arr const char* auto func1 = function void (*)(int, double) auto& arr2 = arr const char[6] auto& func2 = function void (&)(int, double) 위의 예제들에서 볼 수 있듯이, auto의 형식 연역은 템플릿의 형식 연역과 동일하게 작동합니다.
2. 예외 : std::initializer_list<>
더보기C++에서 변수를 초기화 할 때에는 아래와 같은 방법을 이용할 수 있습니다.
int x1 = 27; int x2(27); int x3 = { 27 }; int x4{ 27 };
이 경우, 모든 구문이 명백하게 int입니다.
하지만, 아래와 같이 int를 auto로 변경할 경우
auto x1 = 27; auto x2(27); auto x3 = { 27 }; auto x4{ 27 };
위 구문은 모두 정상적으로 컴파일 되지만, 자료형은 모두 int가 되지 않습니다.
위 구문 중 auto x3 = { 27 }; 부분은 int가 아닌 std::initializer_list<>템플릿 인스턴스로 연역됩니다.
(N3922가 적용되지 않은 C++ 컴파일러는 x4또한 std::initializer_list<>로 연역됩니다.)
이것이 템플릿과 차이가 존재하는 auto의 형식 연역 규칙입니다.
이제 std::initializer_list<>에 대하여 살펴보도록 하겠습니다.
std::initializer_list<>는 위 코드의 x3와 같은 상황에서 연역되는 클래스 템플릿 입니다.
만약 std::initializer_list<>로 연역되지 않는다면, 컴파일이 되지 않습니다.
대표적인 예시는 다음과 같습니다.
auto x3 = { 1, 2, 3.0 };
위의 중괄호 초기치의 값들은 형식이 일관되지 않아 형식 연역이 되지 않습니다.
여기서 주목할 점은, x3의 형식 연역을 위해 두 종류의 형식 연역이 진행된다는 것 입니다.
첫 번째로 auto x3가 std::initializer_list<>로 연역되는 것이고
두 번째로 std::initializer_list<T>의 T가 연역되는 것 입니다.
위의 예제는 두 번째 형식 연역인 std::initializer_list<T>의 T에 대한 연역이 실패하여 컴파일 되지 않는 것 입니다.
중괄호 초기치가 auto로 연역되는 것과, template으로 연역되는 것은 조금 다릅니다.
//Declaration auto x = { 1, 2, 3 }; template<typename T> void function(T param); //Call function({ 1, 2, 3 }); function(x);
위와 같은 함수 호출의 경우, x를 인자로 하는 호출은 정상적으로 컴파일 되지만
중괄호 초기치를 이용한 호출은 컴파일이 되지 않습니다.
중괄호 초기치를 이용한 호출이 필요하다면, 아래와 같이 파라미터를 변경할 수 있습니다.
//Declaration auto x = { 1, 2, 3 }; template<typename T> void function(std::initializer_list<T> param); //Call function({ 1, 2, 3 }); function(x);
위와 같이 파라미터를 변경하게 된다면, T는 int로 연역됩니다.
3. 예외 : 함수와 람다식
더보기기억해두어야 할 예외가 한가지 더 있습니다.
C++14부터, 함수의 반환 형식과 람다식의 매개변수에 auto를 사용하는 것이 가능합니다.
반환형식과 매개변수를 사용자가 아닌 컴파일러가 연역하도록 하는 것 입니다.
그런데 이 때에는, 형식 연역 과정에서 auto가 아닌 템플릿 형식 연역의 규칙이 적용됩니다.
따라서, 다음과 같은 코드는 컴파일 되지 않습니다.
//Function auto function(){ return { 1, 2, 3 }; } //Lambda auto lam = [](auto& val) { }; lam({ 1, 2, 3 });
auto를 사용했지만 형식 연역 규칙은 템플릿과 같이 적용되기 때문에, 중괄호 초기치에 대한 형식 연역을 할 수 없는 소스코드 입니다.
이번 글에서는 auto의 형식 연역 과정을, 템플릿과 비교하며 살펴보았습니다.
템플릿과 거의 동일하지만 몇 가지 다른 부분이 있다는 점에서, 차이점을 확실하게 알아야 할 것입니다.
auto 또한 템플릿과 마찬가지로, 생산성 향상에 도움이 되는 기능입니다.
하지만 auto를 사용함에 있어서 개발자의 의도와 컴파일러의 해석이 달라지는 일이 없도록, 조심스럽고 확실하게 사용해야 할 것입니다.
이 글이 auto를 사용함에 있어서 조금이나마 도움이 되었으면 좋겠습니다.
읽어주셔서 감사합니다.
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] auto와 std::vector, 그리고 Proxy pattern (0) 2022.02.14 [Effective Modern C++] auto의 사용을 고려해야 할 상황들 (0) 2022.01.10 [Effective Modern C++] Type deduction : typeid, boost/type_index (0) 2022.01.06 [Effective Modern C++] Type deduction : decltype (0) 2022.01.04 [Effective Modern C++] Type deduction : Template (0) 2021.12.21