Type Checks in the C++ <type_traits> Library
Explore how compile-time type checks work in the modern C++ <type_traits> library, understand the 14 primary type categories, dive under the hood of template specialization mechanics, and write safer template code.
Modern C++ C++11 / C++17 Metaprogramming
Table of Contents
- Type Checks in the C++
<type_traits>Library
Introduction to Compile-Time Type Checks
The <type_traits> header, introduced in C++11 and significantly enhanced in C++14 and C++17, forms the backbone of compile-time template metaprogramming. It enables developers to inspect, query, and modify type properties during compilation—eliminating runtime overhead while maximizing type safety.
The 14 Primary Type Categories
In C++, every single type belongs to exactly one primary type category. The <type_traits> library provides class templates to check for each category at compile time. Each trait evaluates to a boolean constant exposed via the ::value member (or _v variable template helper in C++17).
Below is the complete list of all 14 primary type categories:
std::is_void<T>— Checks ifTisvoid.std::is_integral<T>— Checks ifTis an integral type (e.g.,int,char,bool,long).std::is_floating_point<T>— Checks ifTis a floating-point type (float,double,long double).std::is_array<T>— Checks ifTis an array type of known or unknown bound.std::is_pointer<T>— Checks ifTis a raw pointer type (excluding member pointers).std::is_null_pointer<T>— Checks ifTisstd::nullptr_t.std::is_member_object_pointer<T>— Checks ifTis a pointer to a non-static data member.std::is_member_function_pointer<T>— Checks ifTis a pointer to a non-static member function.std::is_enum<T>— Checks ifTis an enumeration type (scoped or unscoped).std::is_union<T>— Checks ifTis a union type.std::is_class<T>— Checks ifTis a non-union class/struct type.std::is_function<T>— Checks ifTis a function type.std::is_lvalue_reference<T>— Checks ifTis an lvalue reference (T&).std::is_rvalue_reference<T>— Checks ifTis an rvalue reference (T&&).
Under the Hood: How Type Traits Work
The core mechanics of standard type traits rely on two main C++ metaprogramming concepts: base helper structures (std::integral_constant) and explicit template specialization.
1. Base Helper (std::integral_constant)
std::integral_constant wraps a compile-time value of a specified type into a distinct C++ type. The standard library provides two predefined typedefs for boolean values:
namespace std {
template <class T, T v>
struct integral_constant {
static constexpr T value = v;
using value_type = T;
using type = integral_constant<T, v>;
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; }
};
// Standard helper typedefs for boolean flags:
using true_type = integral_constant<bool, true>;
using false_type = integral_constant<bool, false>;
}2. Primary Template (Default Fallback)
The generic template acts as a default fallback. It inherits from std::false_type, asserting that unknown or unspecialized types do not match the trait.
template <class T>
struct is_integral : public std::false_type {};3. Explicit Specializations
Target types are explicitly specialized to inherit from std::true_type.
template <> struct is_integral<int> : public std::true_type {};
template <> struct is_integral<unsigned int> : public std::true_type {};
template <> struct is_integral<char> : public std::true_type {};
template <> struct is_integral<signed char> : public std::true_type {};
template <> struct is_integral<unsigned char> : public std::true_type {};
template <> struct is_integral<short> : public std::true_type {};
template <> struct is_integral<unsigned short> : public std::true_type {};
template <> struct is_integral<long> : public std::true_type {};
template <> struct is_integral<unsigned long> : public std::true_type {};
template <> struct is_integral<long long> : public std::true_type {};
template <> struct is_integral<unsigned long long> : public std::true_type {};
template <> struct is_integral<bool> : public std::true_type {};
// ... (and cv-qualified variations)Compilation Resolution Workflow
When the compiler evaluates is_integral<int>::value:
- The compiler checks for an explicit template specialization for
int. - It finds
struct is_integral<int>, which inherits fromstd::true_type. std::true_type::valueresolves totrue.
When evaluating is_integral<double>::value:
- No explicit specialization matches
double. - The compiler falls back to the primary template
template <class T> struct is_integral. - It inherits from
std::false_type, so::valueresolves tofalse.
Click to view custom implementation example of is_pointer
#include <iostream>
#include <type_traits>
// Custom implementation of is_pointer
template <typename T>
struct MyIsPointer : std::false_type {};
template <typename T>
struct MyIsPointer<T*> : std::true_type {};
int main() {
std::cout << std::boolalpha;
std::cout << "int is pointer: " << MyIsPointer<int>::value << "\n"; // false
std::cout << "int* is pointer: " << MyIsPointer<int*>::value << "\n"; // true
return 0;
} Modern C++ Convenience: Variable Templates (_v Suffix)
Prior to C++17, accessing trait values required appending ::value to the template instantiation:
// C++11 syntax
bool is_int = std::is_integral<T>::value;Introduced in C++17, variable templates provide a cleaner and less verbose alternative using the _v suffix shortcut:
// C++17 inline variable template helper
template <class T>
inline constexpr bool is_integral_v = is_integral<T>::value;
// C++17 syntax usage
bool is_int = std::is_integral_v<T>;Composite Type Categories, Properties, and Queries
Beyond the 14 primary type categories, <type_traits> includes traits for composite categories, type properties, and numerical queries.
1. Composite Type Categories
Composite categories are constructed by combining two or more primary type categories.
std::is_fundamental<T>— Checks ifTis fundamental (arithmetic types,void, orstd::nullptr_t).std::is_arithmetic<T>— Checks ifTis integral or floating-point.std::is_object<T>— Checks ifTis an object type (types that are not functions, references, orvoid).std::is_reference<T>— Checks ifTis an lvalue or rvalue reference.std::is_compound<T>— Checks ifTis non-fundamental (array, function, pointer, class, union, enum, etc.).
2. Type Properties
Traits that inspect internal capabilities, construction rules, or qualifiers:
std::is_const<T>/std::is_volatile<T>— CV-qualifier checks.std::is_empty<T>— Checks ifTis a class with no non-static data members.std::is_polymorphic<T>— Checks ifThas at least one virtual function.std::is_copy_constructible<T>— Checks ifTcan be copy-constructed.std::is_trivially_copyable<T>— Checks ifTcan be copied byte-for-byte safely (std::memcpy).
3. Type Property Queries
Traits that return numeric constant values rather than boolean flags:
std::alignment_of<T>::value(orstd::alignment_of_v<T>) — Returns alignment requirements in bytes.std::rank<T>::value(orstd::rank_v<T>) — Returns the number of dimensions of an array type.std::extent<T, N>::value(orstd::extent_v<T, N>) — Returns the size of the $N$-th dimension of an array.
Click to view practical application example
#include <iostream>
#include <type_traits>
template <typename T>
void inspect_type() {
std::cout << std::boolalpha;
std::cout << "Is arithmetic: " << std::is_arithmetic_v<T> << "\n";
std::cout << "Is fundamental: " << std::is_fundamental_v<T> << "\n";
std::cout << "Is polymorphic: " << std::is_polymorphic_v<T> << "\n";
std::cout << "Alignment requirement: " << std::alignment_of_v<T> << " bytes\n";
std::cout << "Array rank: " << std::rank_v<T> << "\n";
}
int main() {
std::cout << "--- int[10][20] ---\n";
inspect_type<int[10][20]>();
return 0;
}