본문 바로가기
개발/C++ (98,03,11,14,17,20,23)

Modern C++ : Type Traits 1 (11, 14, 17, 20)

by snowoods 2025. 9. 24.

Modern C++

 

Type Traits 1

 

개요

Type traits는 C++ 템플릿 메타프로그래밍의 핵심 기능 중 하나로, 컴파일 시간에 타입에 대한 정보를 질의하거나 타입을 변환하는 데 사용되는 템플릿 클래스 집합입니다. <type_traits> 헤더 파일에 정의되어 있으며, 이를 통해 코드의 유연성과 안정성을 크게 향상시킬 수 있습니다.

주요 용도는 다음과 같습니다.

  • 타입 검사: 특정 타입이 정수형, 부동소수점형, 클래스 등 특정 카테고리에 속하는지 확인합니다.
  • 타입 속성 확인: 타입이 const 한정자를 가졌는지, 포인터인지 등을 확인합니다.
  • 타입 관계 확인: 두 타입이 동일한지, 한 타입이 다른 타입으로 변환 가능한지 등을 확인합니다.
  • 타입 변환: 타입에 const를 추가하거나 포인터를 제거하는 등의 변환을 수행합니다.

특히 static_assert와 결합하여 컴파일 타임에 템플릿 인자의 유효성을 검사하고, 조건에 맞지 않으면 컴파일 에러를 발생시켜 잘못된 사용을 조기에 방지하는 데 매우 유용합니다.

 

C++ 버전별 주요 키워드 도입 시기

  • C++11: <type_traits> 라이브러리의 대부분이 표준에 도입되었습니다. is_integral, is_floating_point, is_same, enable_if 등 핵심적인 type traits가 포함되었습니다.
  • C++14: _t 접미사를 사용하는 타입 별칭(alias) 템플릿이 추가되었습니다. (예: std::enable_if_t)
  • C++17: _v 접미사를 사용하는 변수 템플릿이 추가되어 ::value 없이 값을 바로 사용할 수 있게 되었습니다. (예: std::is_integral_v). 또한, std::conjunction, std::disjunction, std::negation과 같은 논리 연산자 traits가 추가되어 여러 조건을 조합하기 용이해졌습니다.
  • C++20: Concepts가 도입되어 type traits를 이용한 SFINAE(Substitution Failure Is Not An Error) 기법보다 더 직관적이고 가독성 높은 방식으로 템플릿 제약 조건을 명시할 수 있게 되었습니다.

 

내용 설명

Type traits는 일반적으로 bool 타입의 value 멤버 상수를 가지는 형태로 구현됩니다. 이 값은 컴파일 타임에 결정되며, 조건이 참이면 true, 거짓이면 false가 됩니다.

예를 들어 std::is_integral<T>::valueT가 정수 타입( int, char, short 등)일 경우 true를, 아닐 경우 false를 가집니다. C++17부터는 std::is_integral_v<T> 와 같이 _v 접미사를 붙여 더 간결하게 사용할 수 있습니다.

 

논리 연산 Traits (C++17)

여러 개의 type traits 조건을 조합할 때 유용한 논리 연산자 traits가 있습니다.

  • std::disjunction<Traits...>: 논리합(OR) 연산. 나열된 Traits 중 하나라도 true이면 true가 됩니다.
  • std::conjunction<Traits...>: 논리곱(AND) 연산. 나열된 Traits가 모두 true여야 true가 됩니다.

이를 사용하면 복잡한 static_assert 조건을 간결하게 표현할 수 있습니다.

 

예제 코드

#include <iostream>
#include <type_traits>

// is_numeric: T가 정수 또는 부동소수점 타입인지 확인하는 사용자 정의 trait
// std::disjunction: 여러 trait 중 하나라도 참이면 참 (OR 연산)
template <typename T>
struct is_numeric
    : public std::disjunction<std::is_integral<T>, std::is_floating_point<T>>
{
};

// max1: C++17의 std::disjunction_v를 사용하여 숫자 타입인지 검사
template <typename T>
auto max1(const T &a, const T &b)
{
    // static_assert: 컴파일 타임에 조건을 검사하여 만족하지 않으면 컴파일 에러 발생
    static_assert(
        std::disjunction_v<std::is_integral<T>, std::is_floating_point<T>>,
        "max1: T must be an integral or floating-point type.");

    return (a < b) ? b : a;
}

// max2: 사용자 정의 trait인 is_numeric을 사용하여 검사
template <typename T>
auto max2(const T &a, const T &b)
{
    static_assert(is_numeric<T>::value, "max2: T must be a numeric type.");

    return (a < b) ? b : a;
}

// max3: 두 개의 다른 타입을 받아 각각 숫자 타입인지 검사
// std::conjunction_v: 여러 trait가 모두 참이어야 참 (AND 연산)
template <typename T, typename U>
auto max3(const T &a, const U &b)
{
    static_assert(std::conjunction_v<is_numeric<T>, is_numeric<U>>,
                  "max3: Both T and U must be numeric types.");

    // 다른 타입 간의 비교를 위해 공통 타입으로 변환 후 비교
    using CommonType = std::common_type_t<T, U>;
    return (static_cast<CommonType>(a) < static_cast<CommonType>(b)) ? static_cast<CommonType>(b) : static_cast<CommonType>(a);
}

int main()
{
    std::cout << "--- max1 (single type) ---" << std::endl;
    std::cout << max1(10, 11) << std::endl;
    std::cout << max1(10.0F, 11.0F) << std::endl;
    std::cout << max1(10.0, 11.0) << std::endl;
    std::cout << max1<short>(10, 11) << std::endl;
    // max1("a", "b"); // 컴파일 에러 발생

    std::cout << "\n--- max2 (using custom trait) ---" << std::endl;
    std::cout << max2(10, 11) << std::endl;
    std::cout << max2(10.0F, 11.0F) << std::endl;
    std::cout << max2(10.0, 11.0) << std::endl;
    std::cout << max2<short>(10, 11) << std::endl;

    std::cout << "\n--- max3 (multiple types) ---" << std::endl;
    std::cout << max3(10, 11.0) << std::endl;       // int, double -> double
    std::cout << max3(12.5f, 11) << std::endl;      // float, int -> float

    return 0;
}

 

실행 결과

--- max1 (single type) ---
11
11
11
11

--- max2 (using custom trait) ---
11
11
11
11

--- max3 (multiple types) ---
11
12.5

 

활용팁

  • SFINAE(Substitution Failure Is Not An Error): std::enable_if와 같은 type traits를 사용하여 특정 조건이 만족될 때만 템플릿 함수나 클래스가 오버로드 확인 대상에 포함되도록 할 수 있습니다. 이는 복잡한 템플릿 오버로딩을 구현할 때 강력한 도구입니다.
  • 코드 최적화: std::is_trivially_copyable과 같은 traits를 사용하여 타입의 특성에 따라 memcpy와 같은 빠른 저수준 함수를 사용할지, 아니면 복사 생성자를 호출할지 결정하는 등 런타임 성능을 최적화할 수 있습니다.
  • 가독성 및 유지보수: 템플릿 코드에 static_assert와 type traits를 적극적으로 사용하면, 템플릿을 사용하는 개발자에게 명확한 제약 조건을 알려주고, 의도치 않은 타입으로 템플릿이 인스턴스화되는 것을 막아 디버깅이 어려운 컴파일 에러를 방지할 수 있습니다.
  • C++20 Concepts: C++20 이상을 사용한다면, requires 절과 Concepts를 사용하여 type traits 기반의 제약 조건을 훨씬 더 간결하고 명확하게 표현하는 것을 권장합니다. Concepts는 내부적으로 type traits를 사용하여 구현되는 경우가 많습니다.