템플릿 형식 연역 규칙
template<typename T>
void f(ParamType param);
f(expression);
- 컴파일러는 expression변수를 이용하여 두 가지 형식을 연역한다. T에 대한 형식, ParamType에 대한 형식이다.
두 형식이 다를 때가 있다.
ParamType이 포인터 또는 참조 형식이지만 보편 참조는 아닐 때
template<typename T>
void f(T& param);
int x = 3; //x는 int
const int cx = x; //cx는 const int
const int& rx = x; //rx는 const int인 x에 대한 참조
f(x); //T는 int, param의 형식은 int&가 된다.
f(cx); //T는 const int, param의 형식은 const int&
f(rx); //T는 const int, param의 형식은 const int&
- cx와 rx는 const 값이 배정되었기 때문에 T가 const int로 연역되었다.
- rx의 형식이 참조였지만 T는 비참조로 연역되었다. 형식 연역 과정에서 rx의 참조성이 무시되기 때문이다.
template<typename T>
void f(T* param);
int x = 3; //x는 int
const int *px = x; //px는 const int로서의 x를 가리키는 포인터
f(&x); //T는 int, param의 형식은 int*가 된다.
f(&px); //T는 const int, param의 형식은 const int*
- 포인터일 때는 위와 같다.
- 덮어쓰기를 한다고 보면 된다.
ParamType이 보편 참조일 때
template<typename T>
void f(T&& param); //param이 보편 참조
int x = 3; //x는 int
const int cx = x; //cx는 const int
const int& rx = x; //rx는 const int인 x에 대한 참조
f(x); //x는 왼값, T는 int, param의 형식은 int&가 된다.
f(cx); //cx는 왼값, T는 const int, param의 형식은 const int&
f(rx); //rx는 왼값, T는 const int, param의 형식은 const int&
f(27); //27은 오른값, T는 int, param의 형식은 int&&
- 함수에 들어가는 expression이 왼값이면, T와 ParamType 둘 다 왼값 참조로 연역된다. 이는 이중으로 비정상적인 상황이다.
- T가 참조로 연역되는 경우는 이것이 유일하다.
- ParamType이 선언 구문은 오른 값 참조와 같은 모습이지만, 연역된 형식은 왼값 참조이다.
- 보편 참조는 rvalue 참조 또는 lvalue 참조 둘 다 될 수 있다는 것을 의미한다.
- 27의 경우 int에 나머지 &&가 붙었다.
ParamType이 포인터도 아니고 참조도 아님
template<typename T>
void f(T param); //param이 값으로 전달된다.
int x = 3; //x는 int
const int cx = x; //cx는 const int
const int& rx = x; //rx는 const int인 x에 대한 참조
f(x); //T와 param의 형식은 int
f(cx); //T와 param의 형식은 int
f(rx); //T와 param의 형식은 int
- param은 주어진 인수의 복사본, 완전히 새로운 객체이다.
- expression의 형식이 참조이면 참조부분은 무시된다.
- expression가 const이면 그 const도 무시한다, volatile도 무시한다.
template<typename T>
void f(T param); //param이 값으로 전달된다.
const char* const ptr = "Hello world."; //ptr은 const 객체를 가리키는 const 포인터
f(x); //const char* const 형식의 인수를 전달
- ptr을 f에 전달하면 그 포인터를 구성하는 비트들이 param에 복사된다. (포인터 자체(ptr)는 값으로 전달된다.)
- 형식 연역에서 ptr의 const성은 무시된다.
- param에 연역되는 형식은 const char*이다. param은 const문자열을 가리키는 수정 가능한 포인터이다.
- ptr을 가리키는 것의 const성은 보존되나, ptr자체의 const성은 ptr을 복사해서 새 포인터 param을 생성하는 도중에 사라진다.
배열 인수
template<typename T>
void f(T& param); //참조 전달 매개 변수가 있는 템플릿
const char name[] = "Hello World.";
f(name);
- T에 대해 연역된 형식은 배열의 실제 형식이 된다.
- f의 매개변수의 형식은 const char(&)[13]으로 연역이 된다.
함수 인수
void someFunc(int, double);
template<typename T>
void f1(T param);
template<typename T>
void f2(T& param);
f1(someFunc); //param은 함수 포인터로 연역된다. 형식은 void (*)(int, double)
f2(someFunc); //param은 함수 참조로 연역된다. 형식은 void (&)(int, double)
auto의 형식 연역 규칙
auto x = 27; //x는 포인터도 아니고 참조도 아님
const auto cx = x; //cx 역시 둘 다 아님
const auto& rx = x; //rx는 보편 참조가 아닌 참조
- auto의 형식 연역 규칙은 한가지 예외를 제외하고 다 템플릿 형식 연역 규칙과 동일하다.
템플릿 형식과는 다른 한가지 예외의 규칙
//모두 값이 23인 int가 생긴다.
int x1 = 23;
int x2(23);
int x3 = { 23 };
int x4{ 23 };
auto x1 = 23; //값이 23인 int
auto x2(23); //값이 23인 int
auto x3 = { 23 }; //형식은 std::initializer_list<int>, 값은 23
auto x4{ 23 }; //형식은 std::initializer_list<int>, 값은 23
- auto로 선언된 변수의 initializer가 중괄호 쌍으로 감싸인 형태이면, 연역된 형식은 std::initializer_list이다.
auto x5 = { 1, 2, 3.0 };
- 위와 같은 경우 중괄호 내의 initializer의 값들의 형식이 다르기 때문에 컴파일이 거부된다.
auto x = { 11, 24, 9 };
template<typename T>
void f(T param);
f({11, 23, 9}); //오류!
- 해당 템플릿 함수에 동일한 중괄호 초기치를 전달하면 형식 연역이 실패해서 컴파일이 거부된다.
template<typename T>
void f(std::initializer_list<T> param);
f({11, 23, 9}); //T는 int로 연역되며 initList의 형식은 std::initializer_list로 연역된다.
- 템플릿에 중괄호 초기치를 전달하면 템플릿 형식 연역 규칙들에 의해 T의 형식이 제대로 연역된다.
- auto는 중괄호 초기치가 std::initializer_list를 나타낸다고 가정하지만 템플릿 형식 연역은 그렇지 않다.
c++14부터의 함수 반환 형식
auto createInitList()
{
return {1, 2, 3};
}
- c++14부터는 함수의 반환 형식으로 auto를 사용할 수 있는데 auto를 사용하여도 auto형식 연역이 아닌 템플릿 형식 연역의 규칙들이 적용된다. 그래서 중괄호 초기치를 돌려주는 함수의 반환 형식이 auto일 경우 컴파일이 실패한다.
- 람다식에도 마찬가지로 적용된다.
decltype의 형식연역
Widget w;
const Widget& cw = 2;
auto myWidget1 = cw; //auto 형식 연역
//myWidget1의 형식은 Widget
decltype(auto) myWidget2 = cw; //decltype의 형식 연역
//myWidget2의 형식은 const Widget&
decltype(auto) f1()
{
int x = 0;
...
return x; //decltype(x)는 int이므로 f1은 int를 반환
}
decltype(auto) f2()
{
int x = 0;
...
return (x); //decltype((x))는 int&이므로 f2은 int&를 반환
}
참고하기 좋은 사이트
https://jungwoong.tistory.com/53
'C++ > Effective Modern' 카테고리의 다른 글
우측값(rvalue)에 대하여 (0) | 2024.06.07 |
---|---|
초기화 (1) | 2023.12.06 |