Type Traits: Compile-Time Type Information
The <type_traits> header provides utilities for querying and transforming types at compile time, enabling sophisticated template metaprogramming.
Type Predicates
Query type properties at compile time:
#include <type_traits>
static_assert(std::is_integral_v<int>);
static_assert(!std::is_integral_v<double>);
static_assert(std::is_floating_point_v<float>);
static_assert(std::is_pointer_v<int*>);
static_assert(std::is_array_v<int[10]>);
static_assert(std::is_class_v<std::string>);
Common Type Predicates
| Trait | Checks |
|---|---|
is_void | Type is void |
is_integral | Integer types |
is_floating_point | float, double, long double |
is_arithmetic | Integral or floating-point |
is_pointer | Pointer type |
is_reference | Lvalue or rvalue reference |
is_const | Const-qualified |
is_class | Class or struct |
is_enum | Enumeration type |
Type Relationships
static_assert(std::is_same_v<int, int>);
static_assert(!std::is_same_v<int, long>);
static_assert(std::is_base_of_v<Base, Derived>);
static_assert(std::is_convertible_v<Derived*, Base*>);
Type Transformations
Modify types at compile time:
// Remove const/volatile
using T1 = std::remove_const_t<const int>; // int
using T2 = std::remove_cv_t<const volatile int>; // int
// Add const/volatile
using T3 = std::add_const_t<int>; // const int
// Remove reference
using T4 = std::remove_reference_t<int&>; // int
using T5 = std::remove_reference_t<int&&>; // int
// Remove pointer
using T6 = std::remove_pointer_t<int*>; // int
// Decay (array/function to pointer, remove cv/reference)
using T7 = std::decay_t<int[10]>; // int*
using T8 = std::decay_t<const int&>; // int
Compile-Time Conditionals
std::conditional
Choose type based on condition:
template<typename T>
using StorageType = std::conditional_t<
sizeof(T) <= 8,
T,
T*
>;
StorageType<int> x; // int (small)
StorageType<BigClass> y; // BigClass* (large)
std::enable_if (SFINAE)
Enable function/class only if condition is true:
// Only enable for integral types
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
square(T value) {
return value * value;
}
square(5); // OK
// square(3.14); // Error: SFINAE'd out
Constructibility and Assignability
static_assert(std::is_default_constructible_v<std::string>);
static_assert(std::is_copy_constructible_v<int>);
static_assert(std::is_move_constructible_v<std::unique_ptr<int>>);
static_assert(!std::is_copy_assignable_v<std::unique_ptr<int>>);
// Nothrow versions
static_assert(std::is_nothrow_move_constructible_v<std::string>);
Practical Example: Generic Swap
template<typename T>
void my_swap(T& a, T& b) noexcept(std::is_nothrow_move_constructible_v<T> &&
std::is_nothrow_move_assignable_v<T>) {
T temp = std::move(a);
a = std::move(b);
b = std::move(temp);
}
if constexpr (C++17)
Compile-time conditional branches:
template<typename T>
auto get_value(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t; // Dereference pointer
} else {
return t; // Return value directly
}
}
int x = 42;
auto v1 = get_value(&x); // Dereferences, returns 42
auto v2 = get_value(x); // Returns 42 directly
Discarded Branches
Code in discarded branches doesn’t need to be valid:
template<typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << value * 2; // Only instantiated for integral types
} else {
std::cout << value.toString(); // Only instantiated for types with toString()
}
}
Tag Dispatching
Alternative to SFINAE using type tags:
template<typename Iterator>
void advance_impl(Iterator& it, int n, std::random_access_iterator_tag) {
it += n; // O(1) for random access
}
template<typename Iterator>
void advance_impl(Iterator& it, int n, std::input_iterator_tag) {
while (n--) ++it; // O(n) for input iterators
}
template<typename Iterator>
void advance(Iterator& it, int n) {
advance_impl(it, n, typename std::iterator_traits<Iterator>::iterator_category{});
}
Compile-Time Assertions
template<typename T>
class Buffer {
static_assert(std::is_trivially_copyable_v<T>,
"T must be trivially copyable");
static_assert(sizeof(T) <= 64,
"T is too large for inline storage");
T data_[100];
};
void_t: Detecting Members
template<typename, typename = void>
struct has_value_type : std::false_type {};
template<typename T>
struct has_value_type<T, std::void_t<typename T::value_type>> : std::true_type {};
static_assert(has_value_type<std::vector<int>>::value);
static_assert(!has_value_type<int>::value);
Conceptual Check
What does std::decay_t do to a type?
Runtime Environment
Interactive Lab
1#include <iostream>
2#include <type_traits>
3
4template<typename T>
5void check_type(T value) {
6 if constexpr (std::is_integral_v<T>) {
7 std::cout << "Integral: " << value * 2 << '\n';
8 } else if constexpr (std::is_floating_point_v<T>) {
9 std::cout << "Floating: " << value / 2 << '\n';
10 } else {
11 std::cout << "Other type\n";
12 }
13}
14
15int main() {
16 check_type(42);
17 check_type(3.14);
18 check_type("hello");
19
20 static_assert(std::is_same_v<std::decay_t<int&>, int>);
21 std::cout << "All assertions passed\n";
22
23 return 0;
24}
System Console
Waiting for signal...