C++0x 미리보기 12, 표현식의 형식을 알려주는 decltype
auto보다 일반적인 decltype을 설명합니다.
- 제안의 명칭: Decltype
- 제안자: J. Ja:rvi, B. Stroustrup, D. Gregor, J. Siek, G. Dos Reis
- 관련 문서: N2343,
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]”에 나와 있으니 참고하세요.
-
제가 번역한 C++ 표준 라이브러리 확장: 튜토리얼 및 레퍼런스의 “6.4 result_of 클래스 템플릿”에서 다소 장황하면서도 뭔가 변명조로 설명하는 부분이 바로 이 부분입니다. ↩
예전 댓글(읽기 전용)
-
예 어차피 지금의 C++ 컴파일러들도 함수의 반환값이 함수의 (선언된)반환 형식과 호환되는지를 점검해 주니, 추론이 가능한가 자체는 의문시할 필요가 없을 것 같습니다.
다만 문법 차원에서, 예를 들어 반환값이 없는 함수라도 선언시 void를 꼭 써줘야 한다는 것과 비슷한 어떤 제약이 있을지도 모르겠습니다...
-
ㅠㅠ.., 2011-03-09 13:03 :
한가지 여쭐것이 있는데요.. 제가 하는 프로젝트가 모든 클래스를 만들어놓고 나가는 프로그램마다 사용되는 클래스가 달라지는 방식입니다. 그래서 모든 클래스를 등록시켜놓고 DB에서 클래스 명을 읽어서(Text형태) 등록된 모든 클래스의 클래스 명을 얻는 GetClassName 함수를 통해서 클래스 명과 일치하는 녀석의 인스턴스를 가지고 새로운 객체를 만들어야 되는데요.......
예를들어서 if(!wcscmp(pAnyClass->GetClassName(), strNeedClass)) { auto pNew = new decltype(pAnyClass); AnyContainer.push((CBaseOfAll)pNew); }
이런식이라고 치면.. 위에서 설명하신대로 선언 타입만을 얻어올 수 있어서 & 참조형태로 바뀌게 되서 new CAnyClass& 이런형태가 되서 안되는 것을 알게되었습니다.
그러면 이와같은 현상을 해결 할 수 없을까요? 정리하자면 목표는 동적생성되어져 있는 포인터 인스턴스를 이용해서 같은 타입의 새로운 클래스를 동적 생성하는 것입니다..
추가적인 질문으로.. 위의 해결이 된다면.. CDerived pDerived= new CDerived; CSuper pSuper = pDerived;
일 경우 pSuper 로 나왔지만 생성은 CDerived 형으로 만드는 것이 가능할까요?
너무 질문이 많아서 죄송합니다~
-
이미 짐작하고 계시겠지만... decltype은 이름 그대로 선언된 형식(코드에 박혀 있는)을 알려주는, 전적으로 컴파일 시점 기능입니다. 따라서 하시고자 하는 일이 decltype 때문에 더 편해질 것 같지는 않습니다.
실행 시점에서 동적으로 형식을 선택해서 객체를 생성하는 것은 C++에서 깔끔하게 구현하기 어려운 과제인데요. 결국은(특정 컴파일러에 의존하는 방식이 아닌 한) switch - case 문이 필요하지 않을까 십습니다...
-
ㅠㅠ, 2011-03-29 10:03 :
결국.. 조금 변형해서 몇줄의 코드를 삽입하는 수고를 이용해서 동적생성하고 있습니다..ㅠ
C++0x 람다는 정말 편하네요..
답변 감사드립니다.^^
하스켈의 경우에는 f x y = x*y라고만해도 f의 반환 자료형을 올바르게 추론해주니까, 비슷한 알고리듬을 쓴다면 C++에서도 가능하지 않을까합니다.