Effective Modern C++ Item 2 내용 정리

18487 단어 cppcpp

Chap 1. 형식 연역 (Type deduction)

Item 2. auto 형식 연역 규칙 (auto type deduction)

📌 Main Point

  • auto의 형식 연역은 대체로 템플릿 형식 연역과 같지만, auto 형식 연역은 중괄호 초기치가 std::initializer_list를 나타낸다고 가정하는 반면 템플릿 형식 연역은 그렇지 않다는 차이가 있음
  • 함수의 반환 형식이나 람다 매개변수에 쓰인 auto에 대해서는 auto 형식 연역이 아니라 템플릿 형식 연역이 적용됨

💡 템플릿 형식 연역과 동일한 auto의 형식 연역

  • Item 1에서 템플릿 형식 연역을 배웠다면 auto 형식 연역의 거의 전부를 이미 알고 있는 것임 (한가지 예외 사항을 빼면 auto 형식 연역과 템플릿 형식 연역이 동일하기 때문)
  • 템플릿 형식 연역과 auto 형식 연역 사이에는 직접적인 대응 관계가 존재하고, 둘을 알고리즘적으로 상호 변환할 수 있음

Ex) Item 1에서 다룬 템플릿 형식 연역과 auto 형식 연역의 관계

template<typename T>
void f(ParamType param);

f(expr); 			// 어떤 표현식으로 f를 호출

👉 f 호출에서 컴파일러는 expr을 이용해서 T의 형식과 ParamType의 형식을 연역함
🔑 auto를 이용해서 변수를 선언할 때 autotemplateT와 동일한 역할을 하며, 변수의 형식 지정자(type specifier)는 ParamType과 동일한 역할을 함

Ex) auto의 형식 연역에 대응되는 템플릿 형식 연역

auto x = 27; 				// x의 형식 지정자는 auto
const auto cx = x; 			// cx의 형식 지정자는 const auto
const auto& rx = x; 			// rx의 형식 지정자는 const auto&

👉 위의 코드에서 xcx, rx의 형식들을 연역할 때, 컴파일러는 마치 선언마다 템플릿 함수 하나와 해당 초기화 표현식으로 그 템플릿 함수를 호출하는 구문이 존재하는 것처럼 (= 아래의 코드처럼) 행동함

template<typename T>
void func_for_x(T param); 		// x의 형식 연역을 위한 개념적인 템플릿
func_for_x(27); 			// 개념적인 호출: param에 연역된 형식 = x의 형식

template<typename T>
void func_for_cx(const T param); 	// cx의 형식 연역을 위한 개념적인 템플릿
func_for_cx(x); 			// 개념적인 호출: param에 연역된 형식 = cx의 형식
                    
template<typename T>
void func_for_rx(const T& param); 	// rx의 형식 연역을 위한 개념적인 템플릿
func_for_rx(x); 			// 개념적인 호출: param에 연역된 형식 = rx의 형식

Ex) auto의 형식 연역 (case 1-3)

auto x = 27; 			// Case 3: x는 포인터도, 참조도 아님

const auto cx = x; 		// Case 3: cx는 포인터도, 참조도 아님

const auto& rx = x; 		// Case 1: rx는 보편 참조가 아닌 참조 

auto&& uref1 = x;		// Case 2: x는 int이자 왼값
				// → uref1의 형식은 int&
                
auto&& uref2 = cx;		// Case 2: cx는 const int이자 왼값
				// → uref2의 형식은 const int&
                
auto&& uref3 = 27;		// Case 2: 27는 int이자 오른값
				// → uref3의 형식은 int&&

🔑 Item 1에서 템플릿 형식 연역을 일반적인 함수 템플릿의 param의 형식 지정자인 ParamType의 특성에 따라 세가지 경우로 나누어서 다뤘음
🔑 auto를 이용한 변수 선언에서는 변수의 형식 지정자가 ParamType의 역할을 하므로, auto 형식 연역 역시 세 가지 경우로 나뉨
✔ Case 1: 형식 지정자가 포인터 또는 참조 형식이지만 보편 참조(universal reference)는 아닌 경우
✔ Case 2: 형식 지정자가 보편 참조인 경우
✔ Case 3: 형식 지정자가 포인터도, 참조도 아닌 경우

Ex) auto의 형식 연역 (배열, 함수)

const char name[] = "R. N. Briggs"; 	// name의 형식은 const char[13]

auto arr1 = name;			// arr1의 형식은 const char*
auto& arr2 = name; 			// arr2의 형식은 const char(&)[13]

void someFunc(int, double); 		// someFunc의 형식은 void(int, double)

auto func1 = someFunc; 			// func1의 형식은 void (*)(int, double)
auto& func2 = someFunc; 		// func2의 형식은 void (&)(int, double)

🔑 Item 1에서 비참조 형식 지정자의 경우 배열과 함수 이름이 포인터로 붕괴하는 방식을 다뤘는데, auto 형식 연역에서도 동일하게 적용됨

💡 auto의 형식 연역 차이점 (vs 템플릿 형식 연역)

  • 위의 예시와 같이 auto의 형식 연역은 템플릿 형식 연역과 똑같이 작동하지만 다른 점이 하나 있음

Ex) 27을 초기 값으로 int 변수를 선언 (auto의 형식 연역 차이점)
🔑 C++98에서는 아래 두 가지 구문이 가능했음

int x1 = 27;
int x2(x1);

🔑 균일 초기화(uniform initialization)를 지원하는 C++11에서는 아래의 구문을 추가로 사용할 수 있음

int x3 = { 27 };
int x4{ 27 };

👉 총 네 가지 구문이 존재하지만, 값이 27int가 생긴다는 점은 모두 동일함
👉 고정된 형식 대신 auto를 이용해서 변수를 선언하는 데에는 몇 가지 장점이 있어서, 위의 구문을 auto로 치환하게 되면 아래와 같은 선언이 됨

auto x1 = 27; 		// 형식은 int, 값은 27
auto x2(x1); 		// 형식은 int, 값은 27
auto x3 = { 27 }; 	// 형식은 std::initializer_list<int>, 값은 { 27 }
auto x4{ 27 }; 		// 형식은 std::initializer_list<int>, 값은 { 27 }

👉 위의 선언들은 모두 문제 없이 컴파일 되지만, 이전과는 의미가 달라진 구문이 생김 (아래 두 구문)
auto로 선언된 변수의 초기치(initializer)가 중괄호 쌍으로 감싸진 형태이면, 연역된 형식은 std::initializer_list
⛔ 만약 그런 형식(= std::initializer_list)을 연역할 수 없으면 (e.g. 중괄호 초기치의 값들의 형식이 서로 다른 경우) 컴파일이 거부됨

auto x5 = { 1, 2, 3.0 }; 	// 오류! std::initializer_list<T>의 T를 연역할 수 없음

🔑 auto 형식 연역과 템플릿 형식 연역은 중괄호 초기치가 관여할 때에만 차이를 보임
🔑 auto로 선언된 변수를 중괄호 초기치로 초기화하는 경우, 연역된 형식은 std::initializer_list의 한 인스턴스
⛔ 해당 템플릿 함수에 동일한 중괄호 초기치를 전달하면 형식 연역이 실패해서 컴파일이 거부됨

auto x = { 11, 23, 9 }; 		// x의 형식은 std::initializer_list<int>

template<typename T> 			// x의 선언에 해당하는 매개변수 선언을 가진 템플릿
void f(T param);

f({ 11, 23, 9 }); 			// 오류! T에 대한 형식을 연역할 수 없음

🔑 param의 형식이 어떤 알려지지 않은 T에 대한 std::initializer_list<T>인 템플릿에 그 중괄호 초기치를 전달하면 템플릿 형식 연역 규칙들에 의해 T의 형식이 제대로 연역됨

template<typename T>
void f(std::initializer_list<T> initList);

f({ 11, 23, 9 }); 		// T는 int로 연역되며, 
				// initList의 형식은 std::initializer_list<int>로 연역됨

✔ auto 형식 연역과 템플릿 형식 연역의 실질적인 차이

⛔ auto는 중괄호 초기치가 std::initializer_list를 나타낸다고 가정하지만, 템플릿 형식 연역은 그렇지 않음
auto를 이용해서 변수를 선언하면서 그 변수를 중괄호 초기치로 초기화할 때에는 변수의 형식이 std::initializer_list로 연역됨

✔ C++11 프로그래밍에서 저지르는 전형적인 실수 중 하나

⛔ 변수를 선언할 때 그 변수가 원래 의도한 형식이 아닌 std::initializer_list로 선언되게 만드는 것

✔ C++14에서의 추가 사항

Ex) 중괄호 초기치를 돌려주는 함수의 반환 형식을 auto로 지정하면 컴파일 실패
🔑 C++14에서는 함수의 반환 형식을 auto로 지정해서 컴파일러가 연역하게 만들 수 있으며, 람다의 매개변수 선언에 auto를 사용하는 것도 가능
⛔ 하지만 auto의 그러한 용법들에는 auto 형식 연역이 아니라 템플릿 형식 연역 규칙들이 적용됨 → 중괄호 초기치를 돌려주는 함수의 반환 형식을 auto로 지정하면 컴파일이 실패

auto createInitList()
{
	return { 1, 2, 3 }; 	// 오류! { 1, 2, 3 }의 형식을 연역할 수 없음
}

⛔ C++14 람다의 매개변수 형식 명세에 auto를 사용하는 경우에도 마찬가지 이유로 컴파일이 실패

std::vector<int> v;auto resetV = 
  [&v](const auto& newValue) { v = newValue; }; 	// C++14resetV({ 1, 2, 3 }); 		// 오류! { 1, 2, 3 }의 형식을 연역할 수 없음

좋은 웹페이지 즐겨찾기