항목1 : 템플릿 형식 연역
형식 연역이란 템플릿, auto등에서 컴파일러가 특정 타입을 유츄해내는 것을 이야기한다. c++은 템플릿, auto, decltype에서 형식 연역이 발생한다. 템플릿의 형식 연역 규칙은 다음과 같다.
template<typename T>
void f(T param)
//const, 레퍼런스, volatile전부 무시됨.
int x = 1;
const int cx = 1;
const int& rx = x; //세가지 모두 T -> int
int* px = &x; // T -> int*
const int* px = &x; // T -> const int*
const int* const px = &x; // T -> const int*
//포인터의 경우 포인터 변수 자체에 대한 const성만 사라짐.
template<typename T>
void f(T& param)
//레퍼런스이기에 const성을 유지함.
int x = 1; // T -> int
const int cx = 1; // T -> const int
const int& rx = x; / T -> const int
int* px = &x; // T -> int*
const int* px = &x; // T -> const int*
const int* const px = &x; // T -> const int* const
//포인터의 경우 포인터 변수 자체에 대한 const성만 사라짐.
//void f(const f& param)이면 위 const가 없는 두타입에 대해서도 const가 붙음
template<typename T>
void f(T&& param)
//왼값이 오는 경우 void f(T& param)인 경우와 T, Param의 타입이 똑같음
//오른값이 오면 T의 값이 왼값의 경우와 같아지며 param의 타입은 T&& 패턴을 가지게 됨.
배열 인수
C++98의 경우 int a[]와 int* a는 같은 타입이다. 하지만 모던 c++에서는 내부적으로 다른 타입을 가진다. 배열 타입의 경우 int a[13] = {...};과 같이 만들어진 경우 13이라는 길이를 타입으로부터 얻을 수 있다.
하지만
const char name[] = "asasd";
const char* cp = name;
을 하면 name의 타입이 포인터로 붕괴되어 cp는 const char*가 된다.
템플릿에서도 다음과 같이 동작한다.
void Func(int Param[]);
//c의 잔제로 int* Param과 동일
template<typename T>
void f(T param)
int x[] = {1,2,3};
f(x); //T는 int*
template<typename T>
void f(T& param)
f(x); //T는 int[3] , param은 int (&)[3] 타입
template<typename T, std::size_t N>
void f(T (&)[N] param)
f(x) // N에 3이 들어옴.
함수 포인터의 경우에도 함수 레퍼런스와 함수 포인터가 가르키는 타입은 완전히 같지는 않다. 하지만 실제 응용에서 이 점때문에 무너가 달리지는 드물다. 참고만 하자.
항목2 : auto 형식 연역
auto의 형식 연역은 템플릿과 거의 유사하다.
auto x = 27; // int
const auto cx = x; // const int
const auto& rx = x; // const int&
//기존 템플릿 형식 연역에서 T자리에 auto가, 전체 타입자리에 param의 타입 패턴이 해당된다고 보면 된다.
템플릿과 다른점은 중괄호 초기화의 경우이다.
auto는 한 타입만 포함된 중괄호 초기화를 받으면 std::initializer_list<T>로 타입을 초기화하고 다른 타입들로 구성된 리스트는 컴파일 오류를 띄운다.
템플릿은 기본적으로 중괄호 초기화를 다이렉트로 받으면 컴파일 에러를 발생시킨다.
std::initializer_list<T> list와 같이 직접적으로 받으면 가능하다.
함수 리턴값, 람다 매개변수
c++14에서는 함수의 리턴값과 람다 매개변수에 auto를 사용할 수 있다.
이때 이 auto의 연역에는 템플릿의 형식 연역 규칙이 적용된다.
즉 중괄호 초기화식으로부터 컴파일 에러를 낸다.
항목3 : decltype 형식 연역
decltype은 특정 표현식의 타입을 출력해서 타입이 들어갈 위치를 채울수 있다.
int x = 1;
decltype(x) y = 1; //y는 int
decltyped은 이름을 넣을경우 이름의 정확한 타입을 출력한다.
decltyped에 이름이 아닌 것을 넣을경우 lvalue reference를 출력한다.
int x = 1;
dectype((x)) y = x; //(x)는 표현식이기에 int&
후행 반환 형식
함수의 리턴값에 auto와 ->를 달면 리턴값을 매개변수 부분 뒤에 적을 수 있다. 이는 다음과 같은 경우 유용하다.
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i) -> decltype(c[i])
decltype(c[i])는 c와 i의 선언 뒤에 와야하기에 후행 반환 형식을 사용해야한다.
C++14는 리턴값에 auto를 그냥 사용가능하므로 다음과 같이도 가능하다.
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{
return c[i];
}
하지만 템플릿 형식 연역 규칙에 의해 참조성이 사라져 (int 컨테이너인 경우) int 레퍼런스가 아닌 복사된 값에 대한 rvalue 가 리턴된다. 외부에서 리턴값에 대입하면 컴파일 에러가난다.
decltype(auto)
decltype(auto)는 auto대신 넣으면 decltype의 형식 연역 규칙으로 auto가 작동한다.
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i)
{
return c[i];
}
int lvalue 레퍼런스가 리턴되어 외부에서 대입 가능하다.
rvalue 컨테이너 매개변수도 처리하고 싶다면 Container&& 보편참조를 사용하면 된다. (항목 24)
항목4 : 연역된 타입 확인법
1. IDE 편집기 : 편하다, IDE에 따라 신뢰가 안간다.
2. 컴파일러 진단 편집 : 일부로 해당 타입으로 에러내어 확인. 정확하다.
3. typeid(x).name() : 안 정확함.
4. boost의 type_id_with_cvr<T>().pretty_name() : 정확함.
참고서적
Modern Effective C++
'프로그래밍 언어 > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 항목 31 ~ 34 : 람다 표현식 (0) | 2024.12.15 |
---|---|
[Effective Modern C++] 항목 23 ~ 30 : 이동, 완벽 전달 (1) | 2024.11.23 |
[Effective Modern C++] 항목 18~22 : 스마트 포인터 (0) | 2024.11.10 |
[Effective Modern C++] 항목 7~17 : 현대적 C++ (2) | 2024.11.03 |
[Effective Modern C++] 항목 5~6 : auto (0) | 2024.11.03 |