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 helpers | if constexpr |
|---|---|---|---|
| GCC | 4.3+ | 7+ (C++17) | 7+ (C++17) |
| Clang | 3.0+ | 3.9+ (C++17) | 3.9+ (C++17) |
| MSVC | 2010+ | 2015+ (C++17) | 2017+ (C++17) |
Related posts
Keywords
C++, type traits, SFINAE, templates, metaprogramming, compile-time, enable_if, if constexpr, void_t
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. C++ type traits: is_integral, remove_reference, SFINAE with enable_if, void_t, and compile-time branches with `i…
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ SFINAE | ‘Substitution Failure Is Not An Error’ 가이드
- C++ enable_if | ‘조건부 컴파일’ 가이드
- C++ Template Lambda | ‘템플릿 람다’ 가이드
Keywords Covered in This Article (Related Search Terms)
This article covers C++, type-traits, templates, SFINAE, metaprogramming.