본문 바로가기

컴퓨터과학

[Effective C++] (2) auto의 형식 연역 규칙 숙지하기

Effective C++

* auto의 형식 연역 규칙

(1)에서 템플릿 형식 연역을 배웠다면, auto 형식 연역에 관해 알아야 할 것들의 거의 전부를 알고 있는 셈인데, 한 가지 기이한 예외를 제외하고는 auto 형식 연역이 곧 템플릿 형식 연역이라고 생각할 수 있기 때문이다. 어떻게 그럴 수 있는가 의문이 들것이다. 템플릿 형식 연역에는 템플릿과 함수 및 매개변수가 관여하지만, auto는 그런 것들을 전혀 다루지 않으니 말이다. auto가 그런 것들을 다루지 않는 것은 사실이지만, 그것은 중요하지 않다. 템플릿 형식 연역과 auto 형식 연역 사이에는 직접적인 대응 관계가 존재하기 때문이다. 그둘을 문자 그대로 알고리즘적으로 상호 변환할 수도 있다.

 

<의사 코드>

template<typename T>

void f(ParamType param);

다음과 같은 일반적인 호출을 예로 들어서 템플릿 형식 연역을 설명한다.

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

f호출에서 컴파일러는 expr을 이용해서 T의 형식과 ParamType의 형식을 연역한다. auto를 이용해서 변수를 선언할 때 auto 는 템플릿의 T와 동일한 역할을 하며, 변수의 형식 지정자(type specifier)는 ParamType과 동일한 역할을 한다. 말보다는 예제로 설명하는 편이 더 쉬울 것이다. 다음 예를 참고하며 설명해 보도록 하겠다.

 

<의사 코드>

auto x = 27;

여기서 x의 형식 지정자는 그냥 auto 자체이다. 반면, 다음 선언에서

const auto cs = x;

형식 지정자는 const auto이다. 그리고 다음 선언에서는

const auto& rx = x;

형식 지정자가 const auto&이다. 이 예들에서 x와 cx, 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의 형식이다

 

앞에서 말했듯이, auto에 대한 형식 연역은 예외 하나(잠시 후에 논의할 내용)를 빼면 템플릿 형식 연역과 동일하다.

(1)에서는 템플릿 형식 연역을 일반적인 함수 템플릿의 param의 형식 지정자인 ParamType의 특성에 따라 세 가지 경우로 나누어서 이야기했었다. auto를 이용한 변수 선언에서는 변수의 형식 지정자가 ParamType의 역할을 하므로, auto 형식 연역 역시 세 가지 경우로 나뉜다.

1) 형식 지정자가 포인터나 참조 형식이지만 보편 참조(universal reference)는 아닌 경우.

2) 형식 지정자가 보편 참조인 경우.

3) 형식 지정자가 포인터도 아니고 참조도 아닌 경우.

 

<의사 코드>

- 1), 3)의 예

auto x = 27; // 경우 3 (x는 포인터도 아니고 참조도 아님)

const auto cs = x; // 경우 3 (cx 역시 둘 다 아님)

const auto& rx = x; // 경우 1 (rx는 보편 참조가 아닌 참조)

- 2)의 예

auto&& uref1 = x; // x는 int이자 왼값이므로 uref1의 형식은 int&

auto&& uref2 = cs; // cs는 const int이자 왼왼값이므로 uref2의 형식은 const int&

auto&& uref3 = 27; // 27은 int이자 오른값이므로 uref3의 형식은 int&&

 

마지막으로 (1)에서 비참조 형식 지정자의 경우 배열과 함수 이름이 포인터로 붕괴되는 방식을 논의했었다면, auto 형식 연역에 대해서도 그러한 붕괴가 일어나는 경우를 살펴본다.

const char name[] = "r. n. briggs"; // name의 형식은 const char*

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)

 

이상 위의 예들에서 봤듯이, auto의 형식 연역은 템플릿 형식 연역과 똑같이 작동한다. 사실상 이들은 동전의 양면같다.

그러나 다른 점이 하나 있다면, 27을 초기 값으로 해서 int 변수를 선언하는 예를 살펴보자. C++98에서는 다음 두 가지 구문이 가능했다.

int x1 = 27;

int x2(27);

균일 초기화(uniform initialization)를 지원하는 C++에서는 위의 두 구문과 더불어 다음과 같은 구문들을 사용할 수도 있다.

int x3 = {27};

int x4{ 27 };

총 네 가지 구문이 존재하나, 결과적으로 값이 27인 int가 생긴다는 점은 모두 동일하다.

 

 

*** Key Point ***

- auto 형식 연역은 대체로 템플릿 형식 연역과 같지만, auto 형식 연역은 중괄호 초기치가 std::initializer_list를 나타낸다고 가정하는 반면 템플릿 형식 연역은 그렇지 않다는 차이가 있다.

- 함수의 반환 형식이나 람다 매개변수에 쓰인 auto에 대해서는 auto 형식 연역이 아니라 템플릿 형식 연역이 적용된다.