[Modern C++] 항목 1: 템플릿 형식 연역 규칙을 숙지하라
카테고리: Cpp
이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 C++, 스콧 마이어스 저자, 류광 번역
📦 1. 형식 연역
👉🏻 항목 1: 템플릿 형식 연역 규칙을 숙지하라
auto는 템플릿 형식 연역 기반으로 작동한다.- 즉,
auto를 활용하기 위해선 템플릿 형식 연역 규칙을 알아야 한다.
template<typename T>
void f(ParamType param)
f(expr);
- 컴파일 중 컴파일러는
expr을 통해T,ParamType에 대한 형식을 연역한다. - 보통 두 형식이 다르다.
ParamType에const나 참조 한정사(&,&&) 수식어가 붙은 경우
template<typename T>
void f(const T& param)
int x = 0;
f(x);
T는int,ParamType은const T&로 연역된다.- 전달된 인수와
T가 항상 같지는 않다.T에 대해 연역된 형식은,expr의 형식과ParamType의 형태에 의존한다.
📌 연역 형태에 대한 3가지 경우
ParamType이 포인터/참조 형식이지만, 보편 참조는 아닌 경우ParamType이 보편 참조인 경우ParamType이 포인터/참조 형식이 아닌 경우
1️⃣ ParamType이 포인터/참조 형식이지만, 보편 참조는 아닌 경우
template<typename T>
void f(T& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // T = int, param = int&
f(cx); // T = const int, param = const int&
f(rx); // T = const int, param = const int&
연역 과정:
expr이 참조 형식이면, 참조 부분을 무시한다.T:const int&→const int- 형식 연역 과정에서 참조성이 무시되기 때문
const는 유지된다.
expr형식을ParamType에 대해 패턴 부합 방식으로 대응시켜,T의 형식을 결정한다.ParamType:const int→T&에 대응 →const int&
template<typename T>
void f(const T& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // T = int, param = const int&
f(cx); // T = int, param = const int&
f(rx); // T = int, param = const int&
param이const에 대한 참조이므로,const가T의 일부로 연역될 필요가 없다.- 형식 연역 과정에서 참조성은 무시된다.
T:const int&→intParamType:int→const int&
2️⃣ ParamType이 보편 참조인 경우
template<typename T>
void f(T&& param);
int x = 27;
const int cx = x;
const int& rx;
// x는 왼값
f(x); // ∴ T = int&, param = int&
// cx는 왼값
f(cx); // ∴ T = const int&, param = const int&
// rx는 왼값
f(rx); // ∴ T = const int&, param = const int&
// 27은 오른값
f(27); // ∴ T = int, param = int&&
연역 과정:
expr이 왼값이면,T와ParamType모두 왼값 참조로 연역된다.- 유일하게 템플릿 형식 연역에서
T가 참조성을 갖는 경우이다. ParamType의 선언 구문은 오른값 참조처럼 보이지만, 연역된 형식은 왼값 참조이다.
- 유일하게 템플릿 형식 연역에서
expr이 오른값이면, 정상적인(1의 경우) 규칙이 적용된다.
3️⃣ ParamType이 포인터/참조 형식이 아닌 경우
template<typename T>
void f(T param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // T = int, param = int
f(cx); // T = int, param = int
f(rx); // T = int, param = int
- 포인터/참조 형식이 아니라면, 값을 통한 전달 상황이다.
- 즉, 새로운 객체(복사본) 이 전달된다.
연역 과정:
expr이 참조 형식이면, 참조 부분을 무시한다.expr이const면 무시된다.expr이volatile이면 무시된다.참조,const,volatile이 무시되는 이유는, 복사본을 전달하는 것이기 때문이다.
예시: const 객체를 가리키는 const 포인터
template<typename T>
void f(T param);
const char* const ptr = "pointers";
f(ptr);
const두 개의 의미에 대해서는 이곳을 참고하자.- 포인터 수정 불가, 포인터 null 배정 불가
- 포인터가 가리키는 값 수정 불가
- 포인터는 값으로 전달된다.
- 연역 과정에 의해
const는 무시된다.- 즉,
param은const char*이 된다.
- 즉,
📐 배열 인수
template<typename T>
void f(T Param);
// 배열은 배열의 첫 원소를 가리키는 포인터로 붕괴한다.
// const char name[] = const char* name
const char name[] = "J. P. Briggs";
const char* ptrToName = name;
f(name); // T = const char*
template<typename T>
void f(T& param)
f(name); // T = const char[13], param = const char(&)[13]
1. ParamType이 포인터/참조 형식이지만, 보편 참조는 아닌 경우에 해당한다.- 이렇게 하면, 배열에 대한 참조로 선언할 수도 있다.
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T(&)[N]) noexcept {
return N;
}
int keyVals[] = {1, 3, 7, 9, 11, 22, 35};
int mappedVals[arraySize(keyVals)];
std::array<int, arraySize(keyVals)> mappedVals;
constexpr로 선언하면, 함수 호출 결과를 컴파일 도중 사용할 수 있다.- 배열에 대한 참조로 선언한 경우, 배열 원소의 개수를 연역하는 템플릿을 만들 수 있다.
🔧 함수 인수
void someFunc(int, double);
template<typename T>
void f1(T param);
template<typename T>
void f2(T& param);
// someFunc는 함수 포인터로 사용
f1(someFunc); // param = void(*)(int, double)
f2(someFunc); // param = void(&)(int, double)
- 함수 형식도 함수 포인터 형태로 붕괴할 수 있다.
추가 내용:
- 왼값 특별 규정, 포인터 붕괴 법칙으로 혼란이 가중되었다.
- 컴파일러에게 어떤 형식을 연역했는지 얻는 방법은 항목 4에서 다룬다.
🧐 정리
- 템플릿 형식 연역 도중, 참조성은 무시된다.
- 보편 참조 매개변수에 대한 형식 연역 과정에서, 왼값 인수들은 특별하게 취급된다.
- 값 전달 방식의 매개변수에 대한 형식 연역 과정에서,
const또는volatile모두 무시된다. - 템플릿 형식 연역 과정에서 배열이나 함수 이름에 해당하는 인수는 포인터로 붕괴한다.
- 단, 해당 인수가 참조를 초기화하는 데 쓰이면, 붕괴하지 않는다.
댓글남기기