한 가지 기이한 예외를 빼면 auto의 타입 추론 규칙은 항목 1의 규칙과 동일하다.
템플릿 타입 추론에서 expr의 표현식을 통해 T와 ParamType의 타입을 추론했었다.
auto를 이용해서 변수를 선언하면 auto는 T, 변수의 타입 지정자는 ParamType과 동일한 역할을 한다.
auto x = 27; // 타입 지정자 = auto
const auto cx = x; // 타입 지정자 = const auto
const auto& rx = x; // 타입 지정자 = const auto&
x, cx, rx의 타입을 추론할 때, 컴파일러는 마치 선언마다 템플릿 함수 하나와 해당 초기화 표현식으로 해당 템플릿 함수를 호출하는 구문이 존재하는 것처럼 행동한다.
auto x = 27; // Case3. x = int
const auto cx = x; // Case3. x = const int
const auto& rx = x; // Case1. x = const int&
auto&& uref1 = x; // Case2. x = 왼값. uref1 = int&
auto&& uref2 = cx; // Case2. cx = 왼값. uref2 = const int&
auto&& uref3 = 27; // Case2. 27 = 오른값. uref3 = int&&
const char name[] = "R. N. Briggs"; // const char[13]
auto arr1 = name; // Case4. arr1 = const char*
auto& arr2 = name; // Case4. arr2 = const char(&)[13]
void someFunc(int, double);
auto func1 = someFunc; // Case5. func1 = void(*)(int, double)
auto& func2 = someFunc; // Case5. func2 = void(&)(int, double)
항목 1에서 템플릿 타입 추론을 ParamType의 특성에 따라 일반적인 경우 3가지 및 추가 2가지로 구분했었는데, auto도 동일하게 구분할 수 있다.
그렇다면 대체 한가지 기이한 예외가 무엇일까?
int x1 = 27;
int x2(27);
int x3 = {27}; // C++11
int x4{27}; // C++11
int를 초기화 하는 방법은 총 4가지가 있고 표현식이 다를지언정 모두 동일한 초기화 과정을 거치고 동일한 타입을 가지게 된다.
auto x1 = 27;
auto x2(27);
auto x3 = { 27 }; // C++11
auto x4{ 27 }; // C++11
그런데 int를 auto로 바꿨더니 결과가 다르게 나온다. 그 이유는 C++11부터 추가된 초기화리스트 때문이다.
초기화리스트 얘기가 나온김에 한가지 더 언급하자면 auto와 중괄호 초기화를 같이 사용하면 타입 추론이 두번 일어나게 된다.
auto x5 = { 1, 2, 3.0 }; // error. initializer_list<T>의 T를 추론할 수 없음
첫번째 타입 추론은 x5에 대한 타입 추론이다. 중괄호 초기화를 사용했으므로 initializer_list<T>로 추론이 되는데 이것 역시 템플릿이기 때문에 T에 대한 추론이 필요해진다.
두번째 타입 추론인 T는 중괄호 초기화의 구성 값 타입을 봐야하는데, 타입이 한 종류가 아니라서 추론이 불가능해진다.
그래서 오류가 발생한다.
/* auto */
auto x = { 11, 23, 9 }; // x = std::initializer_list<int>
/* template */
template<typename T>
void f(T param);
f({ 11, 23, 9}); // error
auto 타입 추론과 템플릿 타입 추론은 중괄호 초기화가 관여할 때에만 차이를 보인다.
auto는 중괄호 초기화가 initializer_list<T> 타입이라고 추론하지만 템플릿은 그렇지 않다.
template<typename T>
void f(std::initializer_list<T> param);
f({ 11, 23, 9}); // T = int, param = std::initializer_list<int>
param의 타입이 initializer_list라면 문제 없다.
auto createInitList() { return { 1, 2, 3}; } // error. 타입 추론 불가능
std::vector<int> v;
...
auto resetV = [&v](const auto& newValue) { v = newValue; };
resetV({ 1, 2, 3 }); // error. 타입 추론 불가능
추가로 C++14부터 함수의 반환 타입이나 람다의 매개변수에 auto를 사용할 수 있다. 하지만 타입 추론 방식이 auto가 아닌 템플릿 타입 추론 방식을 따르게 된다.
◾ auto 타입 추론은 대체로 템플릿 타입 추론과 같지만, auto 타입 추론은 중괄호 초기화가 std::initializer_list를 나타낸다고 가정하는 반면 템플릿 타입 추론은 그렇지 않다는 차이가 있다.
◾ 함수의 반환 타입이나 람다 매개변수에 쓰인 auto에 대해서는 auto 타입 추론이 아니라 템플릿 타입 추론이 적용된다.
'도서 > Effective Modern C++' 카테고리의 다른 글
[2장] 항목 6 : auto가 원치 않은 타입으로 추론될 때에는 명시적 타입의 초기값을 사용하라 (0) | 2022.12.26 |
---|---|
[2장] 항목 5 : 명시적 타입 선언보다는 auto를 선호하라 (0) | 2022.12.24 |
[1장] 항목 4 : 추론된 타입을 파악하는 방법을 알아두라 (0) | 2022.12.24 |
[1장] 항목 3 : decltype의 작동 방식을 숙지하라 (0) | 2022.12.23 |
[1장] 항목 1 : 템플릿 타입 추론 규칙을 숙지하라 (0) | 2022.12.23 |