본문으로 건너뛰기
Previous
Next
C++ Type Traits | <type_traits> Complete Guide

C++ Type Traits | <type_traits> Complete Guide

C++ Type Traits | <type_traits> Complete Guide

이 글의 핵심

C++ type traits: is_integral, remove_reference, SFINAE with enable_if, void_t, and compile-time branches with if constexpr.

What are type traits?

Type traits are compile-time utilities that query and transform types. They enable metaprogramming and generic code that adapts to different types.

#include <type_traits>
#include <iostream>
template<typename T>
void process(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Processing integer: " << value << "\n";
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "Processing float: " << value << "\n";
    } else {
        std::cout << "Processing other type\n";
    }
}
int main() {
    process(42);      // "Processing integer: 42"
    process(3.14);    // "Processing float: 3.14"
    process("text");  // "Processing other type"
}

Basic type queries

Primary type categories

#include <type_traits>
// Integral types
static_assert(std::is_integral_v<int>);
static_assert(std::is_integral_v<char>);
static_assert(std::is_integral_v<bool>);
static_assert(!std::is_integral_v<float>);
// Floating-point types
static_assert(std::is_floating_point_v<float>);
static_assert(std::is_floating_point_v<double>);
static_assert(!std::is_floating_point_v<int>);
// Pointer types
static_assert(std::is_pointer_v<int*>);
static_assert(std::is_pointer_v<char*>);
static_assert(!std::is_pointer_v<int>);
// Array types
static_assert(std::is_array_v<int[10]>);
static_assert(!std::is_array_v<int*>);
// Reference types
static_assert(std::is_reference_v<int&>);
static_assert(std::is_reference_v<int&&>);
static_assert(!std::is_reference_v<int>);

Composite type categories

// Arithmetic (integral or floating-point)
static_assert(std::is_arithmetic_v<int>);
static_assert(std::is_arithmetic_v<double>);
static_assert(!std::is_arithmetic_v<std::string>);
// Scalar (arithmetic, pointer, enum, nullptr_t)
static_assert(std::is_scalar_v<int>);
static_assert(std::is_scalar_v<int*>);
static_assert(!std::is_scalar_v<std::vector<int>>);
// Object types
static_assert(std::is_object_v<int>);
static_assert(std::is_object_v<std::string>);
static_assert(!std::is_object_v<int&>);

Type transformations

Remove qualifiers

#include <type_traits>
// Remove const
using T1 = std::remove_const_t<const int>;  // int
using T2 = std::remove_const_t<const int*>; // const int* (pointer itself not const)
// Remove volatile
using T3 = std::remove_volatile_t<volatile int>;  // int
// Remove cv (const and volatile)
using T4 = std::remove_cv_t<const volatile int>;  // int
// Remove reference
using T5 = std::remove_reference_t<int&>;   // int
using T6 = std::remove_reference_t<int&&>;  // int
using T7 = std::remove_reference_t<int>;    // int
// Remove pointer
using T8 = std::remove_pointer_t<int*>;   // int
using T9 = std::remove_pointer_t<int**>;  // int*

Decay

// Decay: array/function to pointer, remove cv and reference
using T1 = std::decay_t<int&>;          // int
using T2 = std::decay_t<const int&>;    // int
using T3 = std::decay_t<int[10]>;       // int*
using T4 = std::decay_t<int(int)>;      // int(*)(int)

Add qualifiers

// Add const
using T1 = std::add_const_t<int>;  // const int
// Add pointer
using T2 = std::add_pointer_t<int>;  // int*
// Add lvalue reference
using T3 = std::add_lvalue_reference_t<int>;  // int&
// Add rvalue reference
using T4 = std::add_rvalue_reference_t<int>;  // int&&

Type relationships

#include <type_traits>
// Same type
static_assert(std::is_same_v<int, int>);
static_assert(!std::is_same_v<int, long>);
static_assert(!std::is_same_v<int, int&>);
// Convertible
static_assert(std::is_convertible_v<int, double>);
static_assert(std::is_convertible_v<int*, void*>);
static_assert(!std::is_convertible_v<int, std::string>);
// Base of
class Base {};
class Derived : public Base {};
static_assert(std::is_base_of_v<Base, Derived>);
static_assert(!std::is_base_of_v<Derived, Base>);

SFINAE with enable_if

Function overloading

#include <type_traits>
#include <iostream>
// For integral types
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
twice(T value) {
    return value * 2;
}
// For floating-point types
template<typename T>
std::enable_if_t<std::is_floating_point_v<T>, T>
twice(T value) {
    return value * 2.0;
}
int main() {
    std::cout << twice(5) << "\n";      // 10 (integral version)
    std::cout << twice(2.5) << "\n";    // 5.0 (floating version)
}

Return type SFINAE

template<typename T>
auto getValue(T& container) -> decltype(container[0]) {
    return container[0];
}
std::vector<int> vec = {1, 2, 3};
int first = getValue(vec);  // OK: vector has operator[]
// std::list<int> lst = {1, 2, 3};
// auto x = getValue(lst);  // Error: list doesn't have operator[]

if constexpr branches

#include <type_traits>
#include <iostream>
#include <vector>
template<typename T>
void process(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Integer: " << value << "\n";
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "Float: " << value << "\n";
    } else if constexpr (std::is_pointer_v<T>) {
        std::cout << "Pointer: " << value << "\n";
    } else {
        std::cout << "Other type\n";
    }
}
int main() {
    process(42);          // "Integer: 42"
    process(3.14);        // "Float: 3.14"
    int x = 10;
    process(&x);          // "Pointer: 0x..."
    process("hello");     // "Other type"
}

Custom traits with void_t

Detect member types

#include <type_traits>
// Primary template
template<typename T, typename = void>
struct has_value_type : std::false_type {};
// Specialization for types with value_type
template<typename T>
struct has_value_type<T, std::void_t<typename T::value_type>> 
    : std::true_type {};
template<typename T>
inline constexpr bool has_value_type_v = has_value_type<T>::value;
// Usage
static_assert(has_value_type_v<std::vector<int>>);
static_assert(!has_value_type_v<int>);

Detect member functions

#include <type_traits>
#include <utility>
// Detect if type has size() method
template<typename T, typename = void>
struct has_size : std::false_type {};
template<typename T>
struct has_size<T, std::void_t<
    decltype(std::declval<T>().size())
>> : std::true_type {};
template<typename T>
inline constexpr bool has_size_v = has_size<T>::value;
// Usage
static_assert(has_size_v<std::vector<int>>);
static_assert(has_size_v<std::string>);
static_assert(!has_size_v<int>);

Detect container

template<typename T, typename = void>
struct is_container : std::false_type {};
template<typename T>
struct is_container<T, std::void_t<
    typename T::value_type,
    typename T::iterator,
    decltype(std::declval<T>().begin()),
    decltype(std::declval<T>().end()),
    decltype(std::declval<T>().size())
>> : std::true_type {};
template<typename T>
inline constexpr bool is_container_v = is_container<T>::value;
// Usage
static_assert(is_container_v<std::vector<int>>);
static_assert(is_container_v<std::list<double>>);
static_assert(!is_container_v<int>);

Real-world examples

1. Generic serialization

#include <type_traits>
#include <string>
#include <sstream>
template<typename T>
std::string serialize(const T& value) {
    if constexpr (std::is_arithmetic_v<T>) {
        return std::to_string(value);
    } else if constexpr (std::is_same_v<T, std::string>) {
        return "\"" + value + "\"";
    } else if constexpr (std::is_pointer_v<T>) {
        std::ostringstream oss;
        oss << static_cast<const void*>(value);
        return oss.str();
    } else {
        return "<unknown>";
    }
}
// Usage
auto s1 = serialize(42);          // "42"
auto s2 = serialize(3.14);        // "3.140000"
auto s3 = serialize(std::string("hello"));  // "\"hello\""

2. Optimized swap

#include <type_traits>
#include <utility>
template<typename T>
void optimized_swap(T& a, T& b) {
    if constexpr (std::is_trivially_copyable_v<T> && sizeof(T) <= 64) {
        // Fast path for small trivial types
        T temp = a;
        a = b;
        b = temp;
    } else {
        // Use move semantics for larger types
        T temp = std::move(a);
        a = std::move(b);
        b = std::move(temp);
    }
}

3. Type-safe printf

#include <type_traits>
#include <iostream>
template<typename T>
void print_value(const T& value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "%d: " << value;
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "%f: " << value;
    } else if constexpr (std::is_pointer_v<T>) {
        std::cout << "%p: " << value;
    } else {
        std::cout << value;
    }
}
template<typename....Args>
void safe_printf(Args....args) {
    (print_value(args), ...);
    std::cout << "\n";
}
// Usage
safe_printf(42, 3.14, "hello");

Performance implications

Zero runtime cost: All type traits are evaluated at compile time.

// Both produce identical assembly
template<typename T>
T add(T a, T b) {
    if constexpr (std::is_integral_v<T>) {
        return a + b;  // Integer addition
    } else {
        return a + b;  // Floating-point addition
    }
}
int x = add(5, 3);        // Compiles to: add eax, ebx
double y = add(2.5, 1.5); // Compiles to: addsd xmm0, xmm1

Type traits vs C++20 Concepts

Type traits (C++11+)

template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
square(T x) {
    return x * x;
}

Concepts (C++20+)

template<std::integral T>
T square(T x) {
    return x * x;
}

Concepts advantages:

  • More readable
  • Better error messages
  • Can be composed Type traits advantages:
  • Works in C++11/14/17
  • More flexible for complex conditions

Compiler support

Compiler<type_traits>_v helpersif constexpr
GCC4.3+7+ (C++17)7+ (C++17)
Clang3.0+3.9+ (C++17)3.9+ (C++17)
MSVC2010+2015+ (C++17)2017+ (C++17)

Keywords

C++, type traits, SFINAE, templates, metaprogramming, compile-time, enable_if, if constexpr, void_t


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++ type traits: is_integral, remove_reference, SFINAE with enable_if, void_t, and compile-time branches with `i… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, type-traits, templates, SFINAE, metaprogramming 등으로 검색하시면 이 글이 도움이 됩니다.