C++0x 미리보기 12, 표현식의 형식을 알려주는 decltype

Twitter icon류광, 2009-06-27 00:06
auto보다 일반적인 decltype을 설명합니다.

decltype은 인수(피연산자)로 주어진 표현식의 형식을 알려주는 연산자입니다. 형식 이름을 지정해야 하는 곳에 대신 사용할 수 있습니다. 예:

template<typename T, typename U>
void f(T x, U y)
{
    decltype(x*y) temp = x*y;
}

성능 관련 사항 하나: decltype(e)에서 표현식 e는 평가되지 않습니다. 위의 예라면 decltype(xy)에 대해 그 어떤 operator도 실제로 호출되지 않습니다.

그런데 위와 같은 변수 정의문이라면 그냥

auto temp = x*y;

로 하는 게 더 간결합니다. 새로 선언되는 변수의 형식을 지정하는 용도라면 대부분 decltype보다 auto가 더 간단한 해법일 것입니다. 그러나 형식 추론이 변수 선언에만 필요한 것은 아니기 때문에 auto가 decltype을 완전히 대신할 수는 없습니다.

decltype이 필요한 예로 흔히 반환 형식의 지정을 듭니다. 다음 예를 봅시다.

<template typename T, typename U>
??? f(T x, U y) { return x*y; }

??? 부분에 decltype(x*y)를 넣으면 되겠다는 생각이 들 것입니다:

<template typename T, typename U>
decltype(x*y) f(T x, U y) { return x*y; }

그러나 안타깝게도 이는 “선언되지 않은 이름은 사용할 수 없다”는 규칙을 위반한 코드입니다. decltype(x*y)의 x와 y는 아직 등장한 적이 없으니까요. 이 문제를 해결하기 위해 다시 auto가 등장합니다.

<template typename T, typename U>
auto f(T x, U y) -> decltype(x*y) { return x*y; }

이제는 x와 y가 선언 후에 쓰이므로 문제가 없습니다. 함수(인수목록)-> 반환형식 이라는 구문은 C++0x에 새로 도입되는 함수 선언 구문입니다.

한편, 이런 구문을 또 다른 제안인 람다 구문과 통합함으로써 auto의 남용(!)을 줄이자는 제안도 있습니다.

<template typename T, typename U>
[]f(T x, U y) -> decltype(x*y) { return x*y; }

(더 나아가서, return xy;으로부터 반환 형식을 유추할 수도 있지 않느냐는 의견도 논의 중이라고 합니다. 그런 추론이 허용된다면/가능하다면 앞의 두 예 모두에서 -> decltype(xy)를 생략할 수 있습니다.)

이러한 반환 형식 추론 능력은 특히 C++ 표준 라이브러리나 Boost의 여러 라이브러리들 같은 “기반” 라이브러리들을 작성하는 사람들에게 축복입니다. 그 점을 잘 보여주는 것이 N2194 decltype for the C++0x Standard Library 제안입니다. decltype 덕분에 복잡하고 다소 작위적인 규칙들[1]을 대거 제거할 수 있습니다.

decltype이라는 이름과 관련한 이야기. 사실 이쪽에 관심이 있는 사람들은 아마 typeof이라는 이름을 더 기대했을 것입니다. 그러나 typeof를 하나의 확장 기능으로 제공해 온 컴파일러들이 있고, 그 의미론들이 지금 말하는 decltype과 정확하게 일치하는 것은 아니기 때문에 decltype이라는 이름이 선택되었습니다. decltype은 “declared type”을 줄인 것입니다. 즉 “선언된 형식”입니다. 그냥 형식이 아니라 선언된 형식이라는 것이 무슨 차이가 있을까요?

int i = 10;
int& ri = i; // ri의 형식은 int인가 int&인가?

참조는 별칭이며, 따라서 원칙적으로 ri의 형식은 int&가 아니라 int입니다. 그러나 int가 아니라 int&라는 답을 얻는 게 더 바람직한 경우들이 있습니다(특히 템플릿 (메타)프로그래밍 쪽에서). decltype(ri)는 int가 아니라 int&, 즉 ri의 선언문에 명시된 형식을 돌려줍니다. 비슷한 예로,

struct A { double x; }
const A* a = new A();

에서 a가 const A를 가리키는 포인터이므로 a->x의 실제 형식은 const double이지만, decltype(a->x)는 그냥 double입니다.

이상은 다소 단순한 예들이고, 경우에 따라서는 &가 더 붙기도 합니다. 구체적인 규칙들이 N2343의 “Section 7.1.5.2 Simple type specifiers [dcl.type.simple]”에 나와 있으니 참고하세요.


  1. 제가 번역한 C++ 표준 라이브러리 확장: 튜토리얼 및 레퍼런스의 “6.4 result_of 클래스 템플릿”에서 다소 장황하면서도 뭔가 변명조로 설명하는 부분이 바로 이 부분입니다. 

태그: C++ C++0x

comments powered by Disqus

예전 댓글(읽기 전용)