Type Traits 라이브러리

  • 타입에 대한 속성 조사
  • 변형된 타입을 구할 때 사용
  • C++11에서 추가

예시: T가 포인터인지 조사

  • C++11: std::is_pointer<T>::value
  • C++17: std::is_pointer_v<T>

역사

  • 1990년대 말부터 사용
  • 2000년 Boost 라이브러리에서 제공
  • C++11에서 표준으로 도입

is_pointer 구현 예시

  // 기본 템플릿
template<typename T>
struct is_pointer {
    enum { value = false };
};

// 포인터 특수화
template<typename T>
struct is_pointer<T*> {
    enum { value = true };
};
  

meta function

  • 컴파일러가 컴파일 시간에 사용하는 함수
  • 컴파일 시간에 true/false 값을 적용
  • 왜 enum 사용?
    • 구조체 안에서 초기화 코드를 작성하고
    • 컴파일 시간에 값을 알 수 있어야 함
    • C++11 이후에는 static constexpr로 대체 가능

if constexpr (C++17)

  #include <iostream>

// is_pointer 정의
template<typename T>
struct is_pointer {
    enum { value = false };
};

template<typename T>
struct is_pointer<T*> {
    enum { value = true };
};

// printv 함수
template<typename T>
void printv(const T& value) {
    if constexpr (is_pointer<T>::value)
        std::cout << value << " : " << *value << std::endl;
    else
        std::cout << value << std::endl;
}

int main() {
    int n = 10;
    printv(&n);  // OK
    printv(n);   // OK
}
  

int2type

  • 개념: 컴파일 시간에 결정된 상수값을 타입으로 만들기
  • C++11의 integral_constant로 발전
  // int2type
template<int N>
struct int2type {
    enum { value = N };
};

// printv_imp 오버로딩
template<typename T>
void printv_imp(const T& value, int2type<1>) {
    std::cout << value << " : " << *value << std::endl;
}

template<typename T>
void printv_imp(const T& value, int2type<0>) {
    std::cout << value << std::endl;
}

// printv 함수
template<typename T>
void printv(const T& value) {
    printv_imp(value, int2type<is_pointer<T>::value>());
}
  

integral_constant (C++11)

  • 개념: int뿐만 아니라 다른 타입도 타입화
  • 구현:
  // <type_traits> 내부 구현 예시
template<typename T, T N>
struct integral_constant {
    static constexpr T value = N;
    using value_type = T;
    using type = integral_constant;
    constexpr operator value_type() const noexcept { return value; }
    constexpr value_type operator()() const noexcept { return value; }
};

// true_type, false_type 정의
using true_type = integral_constant<bool, true>;
using false_type = integral_constant<bool, false>;

// is_pointer 예시
template<typename T>
struct is_pointer : false_type {};

template<typename T>
struct is_pointer<T*> : true_type {};
  

장점

  • int2type 대신 true_type / false_type 오버로딩 사용하여 아래와 같이 printv_imp를 구현 가능
  // printv_imp 오버로딩
template<typename T>
void printv_imp(const T& value, std::true_type) {
    std::cout << value << " : " << *value << std::endl;
}

template<typename T>
void printv_imp(const T& value, std::false_type) {
    std::cout << value << std::endl;
}

// printv 함수
template<typename T>
void printv(const T& value) {
    printv_imp(value, std::is_pointer<T>());
}
  

is_pointer 구현 (const, volatile 대응)

  namespace mystd {
    namespace detail {
        template<typename T>
        struct is_pointer : std::false_type {};

        template<typename T>
        struct is_pointer<T*> : std::true_type {};
    }

    template<typename T>
    using is_pointer = detail::is_pointer<std::remove_cv_t<T>>;
}

int main() {
    namespace X = mystd;
    std::cout << X::is_pointer<int*>::value << std::endl;
    std::cout << X::is_pointer<int* const>::value << std::endl;
    std::cout << X::is_pointer<int* volatile>::value << std::endl;
    std::cout << X::is_pointer<int* const volatile>::value << std::endl;
}
  

is_pointer_v 구현 원리 (variable template)

  template<typename T>
constexpr bool is_pointer_v = std::is_pointer<T>::value;

// 사용 예
bool b1 = std::is_pointer<int*>::value;
bool b2 = is_pointer_v<int*>;
  

포인터 여부에 따른 구현 방식 정리

  1. if constexpr 사용 (C++17~)
  2. true_type / false_type 오버로딩 (C++11~)
  3. enable_if (C++11~)
  4. concept (C++20~)

추가로 remove_all_pointer 구현

  #include <iostream>

// 기본 템플릿
template<typename T>
struct remove_all_pointer {
    using type = T;
};

// 포인터 특수화 (재귀)
template<typename T>
struct remove_all_pointer<T*> {
    using type = typename remove_all_pointer<T>::type;
};

// alias template
template<typename T>
using remove_all_pointer_t = typename remove_all_pointer<T>::type;

int main() {
    std::cout << typeid(remove_all_pointer<int*>::type).name() << std::endl;
    std::cout << typeid(remove_all_pointer<int**>::type).name() << std::endl;
    std::cout << typeid(remove_all_pointer<int***>::type).name() << std::endl;
}