728x90
반응형

IDE 편집기

IDE(통합 개발 환경)의 코드 편집기 중에는 프로그램 객체(변수, 매개변수, 함수 등) 위에 마우스 커서를 올리면 그 객체의 타입을 표시해 주는 것이 많다.

const int theAnswer = 42;
auto x = theAnswer; // int
auto y = &theAnswer; // const int*

이런 일이 가능하려면 편집기의 코드가 어느 정도는 컴파일 가능한 상태여야 한다. 컴파일러가 코드를 파싱 해서 타입 추론을 수행할 수 있을 정도로 편집기의 코드가 완성되어 있지 않으면 편집기는 요청된 객체의 타입을 표시할 수 없다.

일반적으로 int와 같은 간단한 타입의 경우에는 IDE가 알려준 정보가 쓸만하지만, 좀 더 복잡한 타입이 관여할 때에는 IDE가 표시한 정보가 그리 도움이 되지 않을 수도 있다.

 

컴파일러의 진단 메시지

일반적으로 컴파일러가 추론한 타입을 파악하는 데 효과적인 방법 중 하나는, 원하는 타입 때문에 컴파일에 문제가 발생하게 만드는 것이다. 거의 항상 문제를 보고하는 오류 메시지에는 문제를 일으킨 타입이 언급되어 있다.

우선, 클래스 템플릿 하나를 정의 없이 선언만 해둔다. 이 템플릿을 인스턴스화하려고 하면 인스턴스화할 템플릿 정의가 없어서 컴파일 오류가 발생하고, 해당 오류 메시지에 우리가 원하는 정보가 들어있다.

아래의 예제를 봐보자.

template <typename T> // TD를 선언만 해 둔다; TD는 "Type Displayer(타입 표시기)"를 뜻한다.
class TD;

---
// 다음과 같은 변수 x, y가 있을 때,
const int theAnswer = 42;

auto x = theAnswer;
auto y = &theAnswer;
 
// x와 y의 타입을 알고 싶다면 해당 타입들로 TD를 인스턴스화해보면 된다.

// x와 y의 타입들이 담긴 오류 메시지들이 나온다.
TD<decltype(x)> xType;
TD<decltype(y)> yType;
 
// 이 예제 코드에는 "변수이름Type" 형태의 변수 이름들이 쓰였는데, 이렇게 하면 오류 메시지에서 원하는 변수의 타입을 좀 더 쉽게 찾아낼 수 있다.
// 다음은 위의 코드에 대해 어떤 컴파일러가 토해 낸 오류 메시지의 일부분이다.
error: aggregate 'TD<int> xType' has incomplete type and cannot be defined
error: aggregate 'TD<const int *> yType' has incomplete type and cannot be defined

// 다른 한 컴파일러도 형태는 다르지만 본질적으로 동일한 정보를 제공한다. 
error: 'xType'은(는) 정의되지 않은 class 'TD<int>'을(를) 사용합니다.
error: 'yType'은(는) 정의되지 않은 class 'TD<const int *>'을(를) 사용합니다.

// 서식의 차이는 있지만, 이 기법으로 시험해 본 거의 모든 컴파일러는 유용한 타입 정보를 제공했다.

결론적으로, x의 타입을 알고 싶다면 해당 타입들로 TD(정의 없는 클래스 템플릿)를 인스턴스화 해보면 된다.

 

Runtime Output

// x와 y의 타입에 대해 보여준다.
std::cout << typeid(x).name() << '\n';
std::cout << typeid(y).name() << '\n';

위의 예제에서는 타입이 정확하기 때문에 typeid와 std::type_info::name을 사용하면 그만이라고 생각할 수 있지만, 아쉽게도 std::type_info::name의 정보는 믿을 만하지 않다.

 

좀 더 복잡한 예제를 봐보자.

template <typename T>
void f(const T& param);

std::vector<Widget> createVec(void);
 
const auto vw = createVec();
 
if (!vw.empty())
{
    f(&vw[0]);
    ...
}

template<typename T>
void f(const T& param)
{
    using std::cout;
    cout << "T = " << typeid(T).name() << '\n'; // T의 타입
    cout << "param = " << typeid(param).name() << '\n'; // param의 타입
}

// GNU and Clang compilers
T = PK6Widget
param = PK6Widget

// Microsoft’s compiler
T = class Widget const *
param = class Widget const *

// 컴파일러에 따라 결과가 다르다.
---
 
// 어떤 IDE 편집기는 T의 타입을 다음과 같이 보고한다. 
const std::_Simple_types<std::_Wrap_alloc<std::_Vec_base_types<Widget, std::allocator<Widget> >::_Alloc>::value_type>::value_type *
 
// 그리고 param의 타입은 다음과 같이 보고한다.
const std::_Simple_types<...>::value_type *const &
 
// T에 대한 타입보다는 덜 흉악하지만, 중간의 "..."가 좀 혼동될 것이다. 
// 해당 IDE는 "T의 타입의 일부에 해당하는 모든 것을 생략했음"이라는 뜻을 "..."로 표현했다. 
// 행운이 따른다면, 여러분이 사용하는 IDE는 이보다는 나은 결과를 보여줄 것이다.

표준에 따르면, std::type_info::name은 반드시 주어진 타입을 마치 템플릿 함수에 값 전달 매개변수로서 전달된 것처럼 취급해야 한다.

따라서 항목 1에서 설명하듯이, 값 전달의 경우 만일 타입이 참조이면 참조성이 무시되며, 참조를 제거한 후의 타입이 const 또는 volatile이면 해당 const성 또는 volatile성 역시 무시된다.

그래서 실제로는 const Widget* const&인 param의 타입이 그냥 const Widget*로 보고된다.

마찬가지로 IDE 편집기가 표시하는 타입 정보 역시 믿을만하지 않거나, 최소한 신뢰할 수 있을 만큼 유용하지 않다.

 

std::type_info::name과 IDE가 실패하는 경우에서도 Boost TypeIndex라이브러리(흔히 Boost. TypeIndex)는 성공하도록 설계되어 있다는 점을 반길 것이다.

다음은 정확한 타입 정보를 Boost.TypeIndex를 이용해서 얻는 방법을 보여주는 예제 코드이다.

#include <boost/type_index.hpp>
 
template <typename T>
void f(const T& param)
{
    using std::cout;
    using boost::typeindex::type_id_with_cvr;
    
    // T를 표시
    cout << "T = " << type_id_with_cvr<T>().pretty_name() << '\n';
    
    // param의 타입을 표시
    cout << "param = " << type_id_with_cvr<param>().pretty_name() << '\n';
}
 
// 이 코드가 우리가 원했던 정보를 출력하는 이유는, 함수 템플릿 boost::typeindex::type_id_with_cvr이 자신에게 전달된 타입 인수(우리가 알고자 하는 타입)의 const나 volatile, 참조 한정사들을 그대로 보존하기 때문이다(이름에 "with_cvr"이 붙은 이유가 바로 그것이다).
// type_id_with_cvr은 하나의 boost::typeindex::type_index 객체를 산출하며, 그 객체의 pretty_name 멤버 함수는 타입을 사람이 보기에 좋게 표현한 문자열을 담은 std::string 객체를 돌려준다.
// 이제 이전에 보았던 예제를 다시 보자.

std::vector<Widget> createVec(void); // 팩토리 함수
 
const auto vw = createVec(); // vw를 팩토리 함수의 반환값으로 초기화한다.

if (!vw.empty())
{
    f(&vw[0]); // f를 호출
    ...
}
 
// GNU 컴파일러와 Clang 컴파일러에서, Boost.TypeIndex를 사용하는 예제는 다음과 같은 (정확한)출력을 낸다.
T = Widget const*
param = Widget const* const&
 
// Microsoft 컴파일러의 결과도 본질적으로 동일하다. 
T = class Widget const *
param = class Widget const * const &

 

요약

  • 컴파일러가 추론하는 타입을 IDE편집기나 컴파일러 오류 메시지, Boost TypeIndex 라이브러리를 이용해서 파악할 수 있는 경우가 많다.
  • 일부 도구의 결과는 유용하지도 않고 정확하지도 않을 수 있으므로, C++의 타입 추론 규칙들을 제대로 이해하는 것이 필요하다.
728x90
반응형