auto가 수행하는 작업이 곧 템플릿 인자의 타입을 결정하는 것과 거의 같기 때문에 템플릿 타입 추론에 관해서 잘 모르는 상태라면 이번 글을 읽기 전에 이전에 올린 "템플릿 타입 추론"에 관한 글을 읽고 이해해 두는 것이 좋을 것 같다.
그럼 먼저 auto의 타입 추론과 템플릿의 타입 추론이 얼마나 같은지(사실 약간 다르다) 간단한 설명을 보도록 하자. 먼저 템플릿 함수의 전형적인 예는 아래와 같다.
template<typename T> void f(T param); f(expr); // expr은 임의의 표현식일반적인 auto 선언문은 아래와 같은 형태이다.
auto x = expr; // expr은 임의의 표현식컴파일러는 템플릿 함수 f()에 넘기는 인자 expr의 타입에 기초하여 T의 타입 추론을 수행한다. 이 때는 "템플릿 타입 추론" 방식으로 동작하게 되고 그 동작에 대한 설명은 여기를 참조하기 바란다. 자 그 다음 auto의 예를 보면 바로 느낌이 올 것이다. auto에서도 역시 expr의 타입에 기초하여 타입 추론을 수행하는데 그 규칙은 템플릿의 그것과 동일하다. 즉, auto가 템플릿의 T역할을 하는 것이다.
간단히 몇 가지 비교를 해보면 이해가 더 쉬울 것이다. 아래 예를 보자.
template<typename T> void f1(T param); template<typename T> void f2(T& param); template<typename T> void f3(const T& param); template<typename T> void f4(T&& param); auto a1 = expr; // f1(expr)과 같은 타입 추론 수행 auto& a2 = expr; // f2(expr)과 같은 타입 추론 수행 const auto& a3 = expr; // f3(expr)과 같은 타입 추론 수행 auto&& a4 = expr; // f4(expr)과 같은 타입 추론 수행위 예에서 auto와 T의 역할을 비교해보면 auto가 "템플릿 타입 추론" 방식으로 결정된다는 게 별로 이상한 일이 아님을 알 수 있을 것이다. auto의 타입 추론 방식은 심지어 배열과 함수가 지정됐을 때 조차도 템플릿과 같은 방식으로 타입을 결정한다. 아래 예를 보자.
const char name[] = "R. N. Briggs"; // name의 타입은 const char[13] auto arr1 = name; // arr1의 타입은 const char* auto& arr2 = name; // arr2의 타입은 const char(&)[13] void someFunc(int, double); // someFunc는 function // 타입은 void(int, double) auto func1 = someFunc; // func1의 타입은 void (*)(int, double) auto& func2 = someFunc; // func2의 타입은 void (&)(int, double)템플릿 타입 추론에서 비-참조형과 참조형의 인자에 함수나 배열을 넘겼을 때의 동작은 auto 타입 추론에서도 그대로 적용된다. 즉 비-참조형 인자에 함수나 배열을 넘기면 포인터형으로 바뀌어 추론된다. 이 정도면 "auto 타입 추론"은 "템플릿 타입 추론"과 같다고 할 만 하다.
하지만 auto는 템플릿 타입 추론과 한 가지 다른 점이 있는데 바로 braced initializer가 초기화값으로 주어졌을 때이다. 아래 예를 보면 auto의 타입이 예상과 다르게 결정되는 걸 볼 수 있다.
int x1 = 27; int x2(27); int x3 = { 27 }; // C++11, uniform initialization int x4{ 27 }; // C++11, uniform initialization auto a1 = 27; // 타입은 int, 값은 27 auto a2(27); // 타입은 int, 값은 27 auto a3 = { 27 }; // 타입은 std::initializer_list<int>, 값은 {27} auto a4{ 27 }; // 타입은 std::initializer_list<int>, 값은 {27}C++11에서 소개된 uniform initialization 덕분에 위의 x1, x2, x3, x4가 모두 가능한 선언이다. 하지만 똑같은 방식으로 auto 선언문으로 바꾸면 결과는 달라진다. 주석에서 볼 수 있듯이 a3, a4는 int형이 아니고 std::initializer_list<int>형으로 추론되기 때문이다. braced initializer를 이미 사용해 본 적이 있다면 컴파일러가 상황에 맞춰서 그 값들을 std::initializer_list형으로 생성한다는 걸 이미 알고 있을 것이다. auto 선언문에서도 컴파일러는 braced initializer를 std::initializer_list형으로 바꿔서 타입 추론을 수행한다. 컴파일러의 그런 동작을 이미 알고 있었다면 어느 정도 이해가 되는 상황일 것이다.
위와 같이 auto는 braced initializer를 std::initializer_list형으로 인식하고 타입 추론을 수행하지만 템플릿에 braced initializer를 넘기면 타입 추론을 하지 못하고 컴파일에 실패한다. 이 점이 두 타입 추론의 차이점이다. 즉 아래 예에서 f()의 호출은 실패한다.
// auto 타입 추론 auto x = { 11, 23, 9 }; // x의 타입은 std::initializer_list형으로 추론됨 // 템플릿 타입 추론 template void f(T param); f({ 11, 23, 9 }); // error! 타입 추론을 할 수 없음.
C++14에서는 한 가지 더 짚고 가야할 것이 있다. C++14부터는 auto를 함수의 return 타입과 람다의 인자에서도 사용이 가능한데 이 때는 braced initializer를 std::initializer_list형으로 인식하지 않고 컴파일을 실패하게 된다. 즉 아래 두 가지 경우에 대해서는 (auto 타입 추론이 아닌) "템플릿 타입 추론"을 수행한다.
auto createInitList() { return { 1, 2, 3 }; // error: { 1, 2, 3 }을 타입 추론할 수 없음. }
std::vector<int> v; ... auto resetV = [&v](const auto& newValue) { v = newValue; }; // C++14 ... resetV({ 1, 2, 3 }); // error! { 1, 2, 3 }을 타입 추론할 수 없음.위의 두 예에서 모두 auto는 에러가 난다. "템플릿 타입 추론"과 똑같이 braced initializer를 통해서 타입 추론을 하지 못하는 것을 볼 수 있다. 이렇게 보면 auto가 타입 추론되는 방식은 변수를 선언할 때만 braced initializer를 인식(std::initializer_list)한다는 점을 제외하면 템플릿 타입 추론과 완전히 동일하다.
마지막으로 간단히 요약해 보자면...
- auto 타입 추론은 템플릿 타입 추론과 거의 같지만 braced initializer를 std::initializer_list형으로 인식해서 추론을 수행한다는 점이 다르다. (템플릿 타입 추론은 braced initializer를 통해서는 타입 추론을 실패함)
- auto가 함수의 return 타입, 람다의 인자에 사용되었을 때는 템플릿 타입 추론과 완전히 동일하게 동작한다.(즉 braced initializer를 통한 타입 추론은 실패한다)
0 개의 댓글:
댓글 쓰기