Understanding std::true_type in C++ Metaprogramming

This article provides an overview of std::true_type in C++, examining its role as a fundamental building block in compile-time template metaprogramming. It covers how std::true_type is defined in the standard library, why it is used as a base class when creating custom type traits, and how this inheritance simplifies trait design by providing standard compile-time constants and type definitions.

What Is std::true_type?

Defined in the <type_traits> header, std::true_type is a standard type alias for an instantiation of the template class std::integral_constant:

namespace std {
    using true_type = integral_constant<bool, true>;
}

Because it wraps the boolean value true into a distinct C++ type, it represents a compile-time boolean truth value. It includes several key members:

  • value: A static constexpr bool member equal to true.
  • value_type: A type alias for bool.
  • type: A type alias referring to std::integral_constant<bool, true> itself.
  • operator bool(): A constexpr conversion operator returning true.
  • operator() (since C++14): A constexpr function call operator returning true.

Its counterpart is std::false_type, which is an alias for std::integral_constant<bool, false>.

Using std::true_type as a Base Class

In C++ template metaprogramming, type traits are used to query characteristics of types at compile time. Instead of manually declaring static members inside every custom trait, developers inherit from either std::true_type or std::false_type.

Inheriting from std::true_type automatically equips a custom struct with:

  1. The static compile-time constant value = true.
  2. Standard type aliases (type and value_type).
  3. Implicit conversion operators that allow instances of the trait to be evaluated as booleans.

This reduces boilerplate and ensures custom traits adhere to the Standard Library convention.

Implementation Pattern

The standard idiom for creating a boolean type trait involves two steps:

  1. Define a primary template that inherits from std::false_type.
  2. Define template specializations for types that meet the desired condition, having them inherit from std::true_type.

Here is an example that creates a trait to check if a type is a raw pointer:

#include <iostream>
#include <type_traits>

// 1. Primary template: defaults to false
template <typename T>
struct is_pointer_custom : std::false_type {};

// 2. Partial specialization: matches pointer types and inherits true
template <typename T>
struct is_pointer_custom<T*> : std::true_type {};

int main() {
    // Access via the inherited ::value member
    std::cout << std::boolalpha;
    std::cout << is_pointer_custom<int>::value << '\n';   // Output: false
    std::cout << is_pointer_custom<int*>::value << '\n';  // Output: true

    // Access via inherited implicit conversion
    constexpr is_pointer_custom<double*> check;
    if (check) {
        std::cout << "Type is a pointer\n";               // Output: Type is a pointer
    }
}

Benefits in Metaprogramming

Using std::true_type as a base class offers several distinct advantages:

  • Standardization: Tools, libraries, and language features that expect the standard trait interface (such as std::enable_if, std::void_t, and C++20 concepts) can automatically interact with custom traits.
  • Tag Dispatching: Because std::true_type and std::false_type are distinct types, functions can be overloaded based on them, enabling clean compile-time branch selection without runtime overhead.
  • Consistency: It maintains code uniformity across C++ codebases by matching the behavior of Standard Library traits like std::is_integral and std::is_floating_point.