C++/Modern Effective C++

[Effective Modern C++] 항목 2: auto 타입 추론 이해 - 템플릿 타입 추론과 다른 점

꽉퓨타 2023. 4. 2. 12:21

이 글을 보기 전에 [항목 1]을 읽고 오면 좋다.

링크 - [항목1: 템플릿 타입 추론 이해]

 

[Effective Modern C++] 항목 1: 템플릿 타입 추론 이해

템플릿 타입 추론은 C++11의 주요 feature 중 하나인 auto 타입 추론의 기반이기 때문에 잘 이해해야 한다. function template은 다음과 같이 선언 및 호출할 수 있다. // Pseudo Code // Declaration template void f(Para

kwak-story.tistory.com

auto 타입 추론은 템플릿 타입 추론과 거~의 같다.

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

여기서 auto의 타입 추론은 다음과 같다고 볼 수 있다.

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

func_for_x(27);


template<typename T>
void func_for_cx(const T param);

func_for_cx(x);


template<typename T>
void func_for_rx(const T& param);

func_for_rx(x);

결국 auto 앞에 붙는 const나 레퍼런스 등은 ParamType이고, 실제로 auto가 추론되는 것은 T인 것이다.

그리고 = 뒤에 오는 27, x는 argument의 역할을 한다.

 

[항목 1]에서 3 가지 케이스로 나눴었는데, x와 cx는 케이스 3이고, rx는 케이스 1이다.

 

케이스 2에 대해서도 예시를 들자면

auto&& uref1 = x;
auto&& uref2 = cx;
auto&& uref3 = 27;

universal 레퍼런스이므로 lvalue, rvalue를 구분하는게 중요하다.

x와 cx는 lvalue이므로 uref1은 int&, uref2는 const int&로 추론된다.

27은 rvalue이므로 uref3은 그냥 int&&가 되는 것이다.

 

이처럼 템플릿 타입 추론과 auto 타입 추론은 동일하다. 단 한 가지 예외를 제외하면!

 


예외사항

auto 타입 추론은 중괄호로 initialization하는 것을 std::initializer_list로 추론하는 반면, 템플릿 타입 추론은 추론을 못 한다.

 

C++11에선 uniform initialization을 지원한다. 즉,

// C++98
int x1 = 27;
int x2(27);

// C++11
int x3 = { 27 };
int x4{ 27 };

중괄호 {}를 통한 initialization을 지원한다.

 

int 대신 auto를 쓰면 어떨까. [항목 5]에서 추후 나오겠지만, auto로 타입을 선언하는 것에는 장점이 여럿 있다. 따라서 다음과 같이 써보자.

auto x1 = 27;
auto x2(27);
auto x3 = { 27 };
auto x4{ 27 };

넷 다 컴파일이 정상적으로 되지만, 중괄호 {}를 쓴 세 번째, 네 번째의 auto는 int가 아니라 std::initializer_list<int>로 추론된다!!

 

auto로 선언된 변수가 중괄호로 감싸져 있을 경우, 타입 추론은 std::initializer_list<T>로 된다.

만약 중괄호 안의 변수들의 타입이 다른 경우, 컴파일 에러가 날 것이다.

auto x5 = { 1, 2, 3.0 }; // error! can't deduce T for std::initializer_list<T>

아무튼, auto는 중괄호로 선언하는 것을 std::initialized_list로 추론한다는 것을 알았다. 하지만 템플릿 타입 추론에서는 다르다. 다음과 같은 코드는 컴파일 에러를 낼 것이다.

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

f({ 11, 23, 9 }); // error!

템플릿 타입 추론은 중괄호로 감싸진 argument를 std::initializer_list로 추론하지 못한다. 다음과 같이 명시적으로 선언을 할 경우는 가능하다.

template<typename T>
void f(std::initializer_list<T> initList);

f({ 11, 23, 9 }); // T는 int로 추론된다.

왜 auto 타입 추론은 중괄호에 대해 std::initizerlizer_list로 추론하고, 템플릿은 그렇지 않을까? 딱히 이유는 없다고 한다.. 그냥 특별한 예외이므로 알아둬야 한다!

 

아마 auto로 중괄호 선언을 해서 의도치 않게 std::initializer_list로 잘못 추론을 하게 하는 실수가 많이 나올 수 있을 것 같다. 주의하자!

 

C++14에 대해서는 추가적으로 알아둘 내용이 있다.

 

C++14는 함수의 리턴 타입을 auto로 설정하는 것이 가능하다. 하지만 이때의 auto 추론은 템플릿 타입 추론을 따른다. 즉, 다음과 같은 함수는 컴파일 에러를 발생시킨다!

auto createInitList() {
    return { 1, 2, 3 };		// error! { 1, 2, 3 }에 대한 타입 추론 불가
}

 

또, C++14에서 lambda는 parameter 선언에서 auto를 쓸 수 있지만, 이때도 auto 추론은 템플릿 타입 추론을 따른다. 즉, 다음과 같은 lambda 사용은 역시 컴파일 에러를 발생시킨다!

auto resetV = [&v](const auto& newValue) {
    v = newValue;
};

resetV({ 1, 2, 3 });	// error! { 1, 2, 3 }에 대한 타입 추론 불가

 

요약

  • auto 타입 추론은 템플릿 타입 추론과 보통 같다. 예외는, auto 타입 추론은 중괄호 initialization을 std::initialzer_list로 추론하는 반면, 템플릿 타입 추론은 그렇지 않다는 것이다(컴파일 에러를 낸다).
  • [C++14] auto가 함수 리턴 타입에 들어가거나 lambda parameter로 사용되는 경우 템플릿 타입 추론을 따른다. 즉 중괄호를 std::initializer_list로 추론하지 못한다!