2016년 3월 23일 수요일

Effective Modern C++, Item2: auto의 타입 추론을 이해하자

auto의 타입 추론은 템플릿의 그것과 동일하다. 하지만 이게 어떻게 가능한 것일까?

물론 사용 방법은 다르지만 템플릿 타입 추론과 auto 타입 추론은 직접적으로 연결이 되어있다.

말 그대로 변형 알고리즘을 이용하여 하나에서 다른 하나로 바꾸는 것이다.


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

item1의 템플릿의 param의 타입과 굉장히 흡사하지 않는가? 여기서 타입을 결정하는 것은 auto와 그 주변에 붙어있는 것들이다.

컴파일러는 위와 같은 코드를 마치 템플릿이 사용된 것 처럼 이해한다. 다시 말해 아래와 같은 코드로 바꿀 수 있다는 말이다.

template
void func_for_x(T param);
func_for_x(27);

template
void func_for_cx(const T param);
func_for_cx(x);

template
void func_for_rx(const T& param);
func_for_rx(x);

item 1에서 ParamType에 따라서 3 가지 케이스로 나누어 타입 추론을 이해한 것 처럼 auto에도 동일한 규칙이 적용된다.

케이스 1: 타입 구분자(auto)가 포인터나 레퍼런스이고 유니버셜 레퍼런스가 아닐 때.
케이스 2: 타입 구분자가 유니버셜 레퍼런스 일 때.
케이스 3: 타입 구분자가 포인터나 레퍼런스가 아닐 때.

item 1에서 이야기 한 배열이나 함수 인자의 경우 추론에 사용되었던 규칙들 또한 auto에서 동일하게 적용된다.

"더 이상의 자세한 설명은 생략한다."

하지만 이제 한 가지 예외가 등장한다.

C++98 은 여러분에게 int를 선언하는 두 가지 방법을 제공하였다. 그리고 C++11은 두 가지 방법을 추가한다.

int x1 = 27;

int x2 =(27);

int x3 = {27};

int x4{ 27 };

네 가지 다른 선언이 존재하지만 결과는 같다. 값이 27인 int를 선언하는 것이다.

이 때 auto를 사용하면 편리하고 좋지 않을까? 그래서 아래와 같이 선언해보았다.

auto x1 = 27;

auto x2 =(27);

auto x3 = {27};

auto x4{ 27 };


처음 두 개는 정상적으로 동작한다.(기대한대로)
하지만 뒤의 두 개는 int가 아니라 27이라는 값을 가지고 있는 std::initializer_list<int> 라는 타입으로 추론된다.

이것은 auto의 특별한 타입 추론 규칙 때문이다. 만약 auto로 선언된 변수가 중괄호{}로 감싸져있다면, 추론 타입은 std::initializer_list가 된다.
만약 위의 타입으로 추론될 수 없는 경우라면 에러가 난다!

auto x5 = { 1, 2, 5.0 } // 이건 에러다.

이것이 auto와 템플릿 타입 추론의 유일한 차이점이다.

auto x = { 11, 25, 9 } // 이건 정상 동작한다. std::initilizer_list<int> 가 될 것이다.



template<typename t="">

void f(T param);



f({ 11, 25, 9 }); // 이건 에러다. auto와 동일한 선언이지만 템플릿에서는 타입이 추론될 수 없다.



void f(std::initializer_list<t> initList); // 이처럼 직접 선언해주면 정상 동작한다.


C++11에서 의도하지 않게 std::initializer_list 타입의 변수가 선언되는 실수가 종종 발생한다. {}를 쓸 때는 유의하자.

하지만! C++14에서는 또 하나가 추가된다 ㅠㅠ 그지같은..

함수의 반환 타입을 auto로 선언할 수 있는데, 이 때 사용되는 추론규칙은 auto의 그것이 아니라 템플릿의 그것이다.

따라서 아래와 같은 코드는 에러를 내뿜는다.

auto createInitList()
{
  return { 1, 2, 3 }; // 에러.. 추론안됨!
}


auto가 람다의 인자값으로 쓰였을 때에도 같은 에러가 발생할 수 있다.



std::vector<int> v;

...



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



...



resetV({ 1, 2, 3 }); //삐... 자동으로 추론되지 않는다.



----------

핵심요약

1. auto 타입 추론은 template의 타입 추론과 보통 같다고 볼 수 있다. 하지만 {}를 사용해 선언할 때에는 std::initilizer_list 로 추론되므로 유의하자.

2. 함수의 반환값이나 람다의 인자로 auto가 사용된 경우 auto가 아니라 템플릿의 추론규칙을 사용한다.


댓글 없음:

댓글 쓰기