auto : 템플릿에 대한 형식 영역을 기반으로 작동
- 기본적인 함수 템플릿의 선언
template<typename T>
void f(ParamType param);
f(expr);
이러한 경우, 컴파일 시간에 컴파일러는 expr을 이용해 T에 대한 형식과 ParamType에 대한 2가지 형식을 연역하게 된다.
예를들어
template<typename T>
void f(const T& param); // ParamType 은 const T&
int x = 0;
f(x);
이 경우 T는 int로 연역되지만 ParamType은 const int&로 연역된다.
T에 대해 연역된 형식은 expr의 형식에 의존할 뿐만 아니라 ParamType의 형태에도 의존하게 되며, 총 세가지 종류로 나뉜다.
첫번째, ParamType이 포인터 또는 참조 형식이지만 보편 참조는 아닌 경우
두번째, ParamType이 보편 참조인 경우
세번째, ParamType이 포인터도 아니고 참조도 아닌 경우
첫번째, ParamType이 포인터 또는 참조 형식이지만 보편 참조는 아닌 경우
1. expr이 참조 형식이면 참조부분을 무시
2. expr의 형식을 ParamType에 대한 패턴 부합 방식으로 대응시켜 T의 형식을 결정
template<typename T>
void f(T& param); // param은 참조 형식
int x = 27; // x = int
const int cx = x; // cx = const int
const int& rx = x; // rx = const int인 x에 대한 참조
// 여러 호출에서 param과 T에 대해 연역된 형식
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로 연역된다. 즉 해당 매개변수가 const 에 대한 참조가 되기 때문에 객체의 const-ness는 T에 대해 연역된 형식의 일부가 된다. 그러므로 T& 매개변수에 const객체를 전달해도 안전하다.
셋째 호출에서 rx의 형식이 참조이지만 T는 비참조로 연역되었다. 이는 형식 연역과정에서 rx의 reference-ness가 무시되기 때문이다.
f의 매개변수 형식을 T&에서 const T&으로 바꿀경우, cx와 rx의 const-ness는 유지되지만 param이 const에 대한 참조로 간주되기 때문에 const가 T의 일부로 연역될 필요는 없음
param이 참조가 아니라 포인터인 경우에도 형식 연역은 같은 방식으로 진행된다.
template<typename T>
void f(T* param);
int x = 27;
const int *px = &x; // px는 constint로서의 x를 가리키는 포인터
f(&x); // T는 int, param의 형식은 int*
f(px); // T는 const int, param의 형식은 const int*
두번째, ParamType이 보편 참조
매개변수의 선언은 오른값 참조와 같은 모습이지만 왼값 인수가 전달되면 오른값 참조와는 다른 방식으로 행동한다.
만약 expr이 왼값이면, T와 ParamType 둘 다 왼값 참조로 연역되게 되며,
- 템플릿 형식 영역에서 T가 참조 형식으로 연역
- ParamType의 선언 구문은 오른값 참조와 같은 모습이지만, 연역된 형식은 왼값 참조
만약 expr이 오른값이면, 첫번째 규칙들이 적용된다.
template<typename T>
void f(T&& param); // param이 보편참조
int x = 27;
const int cx = x;
const int& rx = 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&&
보편 참조가 관여하는 경우 왼값 인수와 오른값 인수에 대해 서로 다른 연역규칙들이 적용된다.
세번째, ParaytType이 포인터도 아니고 참조도 아님
인수가 함수에 값으로 전달(pass by value)되는 상황
template<typename T>
void f(T param); // param이 값으로 전달
param은 주어진 인수의 복사본이다. param이 새로운 객체이기 때문에,
1. expr의 형식이 참조이면, 참조 부분은 무시된다.
2. expr의 참조성을 무시한 후 만일 expr이 const이면 const역시 무시한다. 만일 volatile이면 그것도 무시한다.
int x = 27;
const int cx = x;
const int& rx = x;
f(x);
f(cx);
f(rx); // 세가지 경우 모두 T와 param의 형식은 int
param은 cx나 rx의 복사본이므로 cx와 rx가 const값을 지칭하지만 param은 const가 아니다. 즉 param은 독립적인 객체이다. expr을 수정할 수 없다고 해서 복사본까지 수정할 수 없는것은 아니기 때문이다.
template<typename T>
void f(T param); // 인수는 param에 값으로 전달
const char* const ptr = // ptr은 const 객체를 가리키는 const 포인터
"sentence";
f(ptr); // const char * const 형식의 인수를 전달
ptr 선언의 * 오른쪽에 있는 const때문에 ptr 자체는 const가 된다. ptr을 f에 전달하면 비트들이 param에 복사된다. 즉, 포인터 자체(ptr)는 값으로 전달된다. 이 과정에서 ptr의 const성은 무시된다. 결과적으로 param에 대해 연역되는 형식은 const char*이 된다. 즉, ptr가 가리키는 const-ness는 보존되지만 param을 생성하는 도중에 ptr 자체의 const-ness는 사라진다.
배열 인수
배열과 포인터를 맞바꿔 쓸 수 있는 것처럼 보이는 원인은 배열이 배열의 첫 원소를 가리키는 포인터로 붕괴(decay)한다는 것이다.
const char name[] = "sentence"; // name의 형식은 const char[8]
const char * ptrToName = name; // 배열이 포인터로 붕괴된다
const char*와 const char[8]은 서로 같지 않지만 오류 없이 컴파일 된다.
template<typename T>
void f(T param);
const char name[] = "sentence";
f(name); // name은 배열이지만 T는 const char*로 연역된다.
배열 매개변수 선언이 포인터 매개변수처럼 취급되므로 템플릿 함수에 값으로 전달되는 배열의 형식은 포인터 형식으로 연역된다.
배열에 대한 참조를 선언하여 T에 대한 형식이 배열의 실제 형식이 되게 할 수 있다.
template<typename T>
void f(T& param);
f(name);
T는 const char[8]로 연역되고 f의 매개변수 형식은 const char (&)[8]로 연역된다.
함수 인수
함수 형식도 함수 포인터로 붕괴 할 수 있다. 다음 예는 함수 포인터로의 붕괴이다.
void someFunc(int, double); //somdFunc는 하나의 함수, 형식은 void(int, double)
template<typename T>
void f1(T param); // f1의 param은 값 전달 방식
template<typename T>
void f2(T& param); // f2의 param은 참조 전달 방식
f1(someFunc); // param은 함수 포인터로 연역됨, 형식은 void (*)(int, double)
f2(someFunc); // param은 함수 참조로 연역됨, 형식은 void (&)(int, double)
conclusion
1. 템플릿 형식 연역 도중 참조 형식의 인수들은 비참조로 취급된다.
2. 보편 참조 매개변수에 대한 형식 연역 과정에서 왼값 인수들은 특별하게 취급된다.
3. 값 전달 방식의 매개변수에 대한 형식 연역과정에서 const 또는 volatile 인수는 비 const, 비 volatile 인수로 취급된다.
4. 템플릿 형식 연역 과정에서 배열이나 함수 이름에 해당하는 인수는 포인터로 붕괴한다. 단 인수가 참조를 초기화 하는 데 쓰이는 경우에는 포인터로 붕괴하지 않는다.
Reference
Effective Modern C++ 항목 1: 템플릿 형식 연역 규칙을 숙지하라
'C++ > Effective Modern C++' 카테고리의 다른 글
auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용 (0) | 2020.06.28 |
---|---|
명시적 형식 선언보다는 auto를 선호하라 (0) | 2020.06.27 |
연역된 형식을 파악하는 방법 (0) | 2020.06.26 |
decltype의 작동 방식 (0) | 2020.06.26 |
auto 형식 연역 규칙 (0) | 2020.06.24 |
댓글