Trailing Return Type (리턴 타입 추적하기)

간단한 예로 시작해보자. 아래 long 타입을 리턴하는 함수가 있다. 이건 lambda 가 아니라 그냥 함수이다.

auto GetCPUSpeedInHertz() -> long
{
    return 1234567890;
}

보는바와 같이 long 타입을 리턴한다. Trailing Return Type 이라는 새로운 문법을 사용한 예이다. 왼쪽에 있는 auto 키워드는 자리선점의 의미이다. 실제 타입은 → 이후에 표시된 것을 따른다. 예를 한가지 더 보자.

auto GetPI() -> decltype( 3.14 )
{
    return 3.14159;
}

리턴타입이 decltype 으로부터 추론되었다. (위 decltype 키워드를 한번 참조) 위 두가지 예를 보아도 이걸 사용할 필요성을 느낄 수 없을 것이다.

그럼 어디서 쓸만한 걸까?

template 함수를 생각해보자.

template <typename FirstType, typename SecondType>
/*UnknownReturnType*/ AddThem( FirstType t1, SecondType t2 )
{
    return t1 + t2;
}

두 특정 값을 더하여 리턴하는 내용이다. 이제 int 와 double 을 각각 인자로 넣는다면 리턴타입은 어떻게 될까? 아마 double 이라 대답할 것이다. 근데 이것이 SecondType 이 함수의 리턴타입이 되어야 한다는 것을 의미할까?

template <typename FirstType, typename SecondType>
SecondType AddThem( FirstType t1, SecondType t2 );

사실 이걸 단언할 수는 없다. 이 함수가 어떤 타입을 왼쪽 혹은 오른쪽에 적용하여 호출될 지는 아무도 모르기 때문이다. 심지어 더 높은 고 수준의 타입이 사용될 수도 있다. 예를 들어

AddThem( 10.0, 'A' );
AddThem( "CodeProject", ".com" );
AddThem( "C++", 'X' );
AddThem( vector_object, list_object );

또한 AddThem 함수 안에 있는 operator + 는 어쩌면 overloaded 된 다른 멤버함수를 호출할 지도 모른다. 그리고 이 멤버함수는 지금껏 언급되지 않은 또다른 세번째 타입을 리턴할 지도 모른다. 이제 해결책은 아래와 같이 사용하는 것이다.

template <typename FirstType, typename SecondType>
auto AddThem( FirstType t1, SecondType t2 ) -> decltype( t1 + t2 )
{
    return t1 + t2;
}

decltype 설명에서 언급했듯이 타입은 표현식으로 결정이 된다. t1 + t2 에 대한 타입이 결정되는 것이다. 만약 컴파일러가 타입 업그레이드1)가 가능하다면 그렇게 할 것이다. 만약 타입이 class 의 일종이고 overloaded 된 operator + 이 호출된다면 operator + 의 리턴타입이 최종 리턴타입으로 결정될 것이다. 리턴타입이 native (built-in을 뜻함?) 가 아니고 overloaded 된 함수도 찾을 수 없다면 에러를 뱉을 것이다. 이 타입추론이 실제 template 함수를 특정 데이터타입과 함께 인스턴스화 될 때 정해진다는 사실은 매우 중요하다. 이 이전에 컴파일러는 어떠한 체크도 하지 않을 것이다.

1) int 와 double 일 경우 double 타입으로 결정되는 것을 뜻함.