IDE 편집기

객체 위에 커서를 올리면 타입을 표시해 주는 경우가 많다.

이게 가능하려면 코드가 어느 정도는 컴파일이 가능한 상태여야 한다. 그리고 간단한 타입의 경우라면 쓸만하겠지만 좀 더 복잡한 타입이 관여하게 되면 그다지 도움이 되지 않을 수 있다.

 

컴파일러의 진단 메시지

원하는 타입으로 문제를 발생시켜서 오류 메시지를 확인한다.

 

template<typename T>
class TD; // 선언만 한다
...
TD<decltype(x)> xType; // x = int
TD<decltype(y)> yType; // y = const int*

클래스의 정의가 존재하지 않기 때문에 컴파일 오류가 발생하고 친절하게 타입을 알려준다.

 

실행시점 출력

타입 정보를 출력해서 표시하는 방법은 런타임에만 사용할 수 있지만 출력의 서식을 완벽하게 제어할 수 있다는 장점이 있다.

 

std::cout << typeid(x).name() << '\n'; // int
std::cout << typeid(y).name() << '\n'; // const int*

위의 경우만 봤을때는 완벽한 방법으로 보인다. 객체에 typeid를 적용하면 새로운 객체(std::type_info)가 생성되는 것인데, name이 항상 의미있는 결과를 반환해준다는 보장은 없다. 아래의 예를 보자.

 

template<typename T> // Case1. T = const Widget*
void f(const T& param) // Case1. param = const Widget* const&
{
    typeid(T).name(); // Case3. const Widget*
    typeid(param).name(); // Case3. const Widget*. param의 const와 참조성이 떨어져나갔다
}
...
std::vector<Widget> createVec(); // 팩토리 함수
const auto vw = createVec();

if (!vw.empty()) f(&vw[0]); // &vw[0] = const Widget*

템플릿 추론 규칙에 의하면 T는 const Widget*, param은 const Widget* const& 타입이어야 한다.

그런데 type_info::name은 둘다 const Widget*으로 나오게 된다. 왜냐면 type_info::name은 값의 의한 전달이 이루어진것처럼 취급해야 하기 때문이다. 그래서 포인터의 참조성과 상수성이 모두 사라진 const Widget*으로 바뀌어버린다.

실제로 f로 전달된 타입의 추론은 틀리지 않는다.

 

#include <boost/type_index.hpp>

template<typename T>
void f(const T& param)
{
    using boost::typeindex::type_id_with_cvr; // cvr: const, volatile, reference
    
    type_id_with_cvr<T>().pretty_name();
    type_id_with_cvr<decltype(param)>().pretty_name();
}

typeid대신 부스트 라이브러리를 사용하면 전달된 인수의 참조 한정사들을 그대로 보존하기 때문에 정확한 결과를 알려준다.

그래도 항목 1~3의 규칙들을 모두 숙지해서 파악하는것만큼 좋은게 없다.

 

◾ 컴파일러가 추론하는 타입을 IDE 편집기나 컴파일러 오류 메시지, Boost TypeIndex 라이브러리를 이용해서 파악할 수 있는 경우가 많다.

◾ 일부 도구의 결과는 유용하지도 않고 정확하지도 않을 수 있으므로, C++의 타입 추론 규칙들을 제대로 이해하는 것은 여전히 필요한 일이다.

+ Recent posts