템플릿 형식 연역과 auto 형식 연역 사이에는 직접적인 대응 관계가 존재한다.
template<typename T>
void f(ParamType param);
f(expr);
auto x = 27; // x의 형식 지정자는 auto
const auto cs = x; // x의 형식 지정자는 const auto
const auto& rx = x; // x의 형식 지정자는 const auto&
x와 cx, rx의 형식들을 연역할 때, 컴파일러는 선언마다 템플릿 함수 하나와 해당 초기화 표현식으로 템플릿 함수를 호출하는 구문이 존재하는 것처럼 행동한다.
template<typename T>
void func_for_x(T param); // x의 형식을 연역하기위한 개념적인 템플릿
func_for_x(27); // 개념적인 호출 : param에 대해 연역된 형식이 x의 형식
template<typename T>
void func_for_cx(const T param);
func_for_cx(x); // 개념적인 호출 : param에 대해 연역된 형식이 cx의 형식
template<typename T>
void func_for_rx(const T& param);
func_for_rx(x); // 개념적인 호출 : param에 대해 연역된 형식이 rx의 형식
예외상황 하나를 제외하고는 템플릿 형식 연역과 같다.
첫째, 형식 지정자가 포인터나 참조 형식이지만 보편 참조(universal reference)는 아닌경우.
둘째, 형식 지정자가 보편 참조인 경우
셋째, 형식 지정자가 포인터도 아니도 참조도 아닌 경우
auto x = 27; // 경우 3
const auto cx = x; // 경우 3
const auto& rx = x; // 경우 1
두번째 경우도 기대한 대로 작동하게 된다.
auto&& uref1 = x; // x는 int이자 왼값이므로 uref1의 형식은 int&
auto&& uref2 = cx; // cx는 const int이자 왼값이므로 uref2의 형식은 const int&
auto&& uref3 = 27; // 27은 int이자 오른값이므로 uref3의 형식은 int&&
템플릿에서도 배열과 함수 이름이 포인터로 붕괴가 발생했고, auto 형식 연역에도 붕괴가 발생한다.
const char name[] = "sentence"; // name의 형식은 const char[8]
auto arr1 = name; // arr1의 형식은 const char*
auto& arr2 = name; // arr2의 형식은 const char (&)[13]
void someFunc(int, double); // someFunc는 함수, 형식은 void(int, double)
auto func1 = someFunc; // func1의 형식은 void (*)(int, double)
auto& func2 = someFunc; // func2의 형식은 void (&)(int, double)
이렇듯 auto의 형식 연역은 템플릿 형식 연역과 똑같이 작동한다.
int x1 = 27;
int x2(27);
int x3 = { 27 }; // 균열 초기화를 지원하는 C++11에서 사용
int x4{ 27 }; // 균열 초기화를 지원하는 C++11에서 사용
표현 방법은 다르지만 결과적으로 값이 27인 int가 생긴다.
하지만 auto에 대한 특별한 연역규칙 때문에
auto x1 = 27; // int, 값은 27
auto x2(27); // int, 값은 27
auto x3 = { 27 }; // std::initializer_list<int>, 값은 { 27 }
auto x4{ 27 }; // std::initializer_list<int>, 값은 { 27 }
초기치가 중괄호 쌍으로 감싸인 상태인 경우 std::initializer_list로 연역이 되었다.
auto로 선언된 변수를 중괄호 초기치로 초기화 하는 경우 연역된 형식은 std::initializer_list의 한 신스턴스이다. 하지만 템플릿 함수에 동일한 중괄호 초기치를 전달하면 형식 연역이 실패해서 컴파일이 거부된다.
auto x = {11, 23, 9}; // x의 형식은 std::initializer_list<int>
template<typename T>
void f(T param);
f({11, 23, 9}); // 오류! T에 대한 형식을 연역할 수 없음
template<typename T>
void f(std::initializer_list<T> initList);
f({11, 23, 9}); // T는 int로 연역되어 initList의 형식은 std::Initializer_list<int>로 연역된다.
즉, auto 형식 연역과 템플릿 형식 연역의 차이는 auto는 중괄호 초기치가 std::initializer_list를 나타낸다고 가정하지만 템플릿 형식 연역은 그렇지 않다는 것뿐이다.
C++14에서는 함수의 반환 형식을 auto로 지정해서 컴파일러가 연역하게 만들 수 있으며, 람다의 매개변수 선언에 auto를 사용하는 것도 가능하다. 그러나 그런 용법에는 템플릿 형식 연역 규칙들이 적용된다.
auto createInitList()
{
return {1, 2, 3}; // 형식을 연역할 수 없기 때문에 오류 발생
}
std::vector<int> v;
...
auto resetV = [&v](onst auto& newValue) { v = newValue; }; // C++14 람다식
...
resetV({1, 2, 3}); // {1, 2, 3}의 형식을 연역할 수 없음
Conclusion
- auto 형식 연역은 대체로 템플릿 형식 연역과 같지만, auto 형식 연역은 중괄호 초기치가 std::initializer_list를 나타낸다고 가정하는 반면, 템플릿 형식 연역은 그렇지 않다
- 함수 반환 형식이나 매개변수에 쓰인 auto에 대해서는 auto 형식 연역이 아니라 템플릿 형식 연역이 적용된다.
Reference
Effective Modern C++ 항목2: auto의 형식 연역 규칙을 숙지하라
'C++ > Effective Modern C++' 카테고리의 다른 글
auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용 (0) | 2020.06.28 |
---|---|
명시적 형식 선언보다는 auto를 선호하라 (0) | 2020.06.27 |
연역된 형식을 파악하는 방법 (0) | 2020.06.26 |
decltype의 작동 방식 (0) | 2020.06.26 |
템플릿 형식 연역 규칙 (0) | 2020.06.23 |
댓글