프로그래밍 언어/Effective Modern C++

[Effective Modern C++] 항목 1~4 : 형식 영역

우향우@ 2024. 11. 3. 18:01

항목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++