~/c++_type_traits_&_template_metaprogramming.log
Apr 14, 2026

C++ Type Traits & Template Metaprogramming

A type trait is typically a template that maps a type T to:

  • A compile-time boolean (e.g., "does TT satisfy some property?")
  • Another type (e.g., "strip references from TT")

Standard Library Foundation

In the Standard Library (<type_traits>), boolean traits are implemented using std::true_type and std::false_type, which derive from std::bool_constant:

#include <type_traits>

using std::true_type;
using std::false_type;

// Conceptual underlying implementation:
template <bool B>
struct bool_constant {
    static constexpr bool value = B;
    constexpr operator bool() const noexcept { return value; }
};

using true_type  = bool_constant<true>;
using false_type = bool_constant<false>;

[!INFO] Key Properties

  • true_type::value evaluates to true; false_type::value evaluates to false.
  • Objects of these types are implicitly convertible to bool, allowing syntax like if constexpr (MyTrait()).

General Pattern for Custom Traits

  1. Provide a primary template that defaults to std::false_type.
  2. Provide specializations that inherit from std::true_type for matching target types.

1.1. Simple Concrete Example: IsInt

Goal: Create a trait IsInt that evaluates to true only for int, and false for everything else.

#include <iostream>
#include <type_traits>

// Primary template: default to false
template <typename T>
struct IsInt : std::false_type {};

// Specialization: int is true
template <>
struct IsInt<int> : std::true_type {};

template <typename T>
void describe_type() {
    if constexpr (IsInt<T>()) {
        std::cout << "This is int\n";
    } else {
        std::cout << "This is NOT int\n";
    }
}

int main() {
    describe_type<int>();   // Prints: This is int
    describe_type<float>(); // Prints: This is NOT int
    return 0;
}

[!NOTE] How if constexpr Works Here
if constexpr evaluates IsInt<T>() at compile time. Only the matching branch is compiled into the binary; the unselected branch is completely discarded.


1.2. Advanced Scenario: Port Filtering

This pattern applies type traits to inspect and filter template parameter packs and tuples at compile time.

1. Defining the Trait

// Primary template: default to false
template <class Tested_T>
struct SatisfiesDiagnosticServicePortConcept : std::false_type {};

// Positive specialization: matches diagnostic service ports
template <class ServiceData_T, diagnostic::types::ComponentId component_id_v>
struct SatisfiesDiagnosticServicePortConcept<
    diagnostic::to_aos::DiagnosticGatewayToSwco<ServiceData_T, component_id_v>
> : std::true_type {};
  • For any type T not matching the template pattern, value is false.
  • For types matching diagnostic::to_aos::DiagnosticGatewayToSwco<ServiceData_T, component_id_v>, value is true.

2. Filtering a Single Port

template <class Port_T>
auto getDiagnosticServicePortOrEmpty(Port_T& port) {
    using InterfaceType = typename diagnostic::utils::SampleValueType<Port_T>::Type;

    if constexpr (SatisfiesDiagnosticServicePortConcept<InterfaceType>()) {
        // Keeps the port: returns std::tuple<Port_T&>
        return std::tuple<Port_T&>(port);
    } else {
        // Drops the port: returns std::tuple<>
        return std::tuple<>{};
    }
}

[!SUCCESS] Compile-time Execution

  • SatisfiesDiagnosticServicePortConcept<InterfaceType>() is evaluated at compile time.
  • If true, the return type is std::tuple<Port_T&>.
  • If false, the return type is std::tuple<>.
  • Zero runtime overhead—only one branch is instantiated per Port_T.

3. Filtering an Entire Tuple

template <typename PortTuple_T>
auto onlyDiagnosticServicePorts(const PortTuple_T& ports) {
    return std::apply(
        [](auto&... port) {
            // For each port, returns either:
            // - std::tuple<Port_T&> (1 element)
            // - std::tuple<>        (empty)
            return std::tuple_cat(getDiagnosticServicePortOrEmpty(port)...);
        },
        ports
    );
}

Breakdown:

  1. std::apply: Unpacks ports into individual arguments passed to the lambda function parameter pack port....
  2. Pack Expansion (...): Evaluates getDiagnosticServicePortOrEmpty(port) for each individual port in sequence.
  3. std::tuple_cat: Concatenates all generated sub-tuples.
  • Valid ports contribute std::tuple<Port&>.
  • Invalid ports contribute std::tuple<> (ignored).
  1. Result: A single flattened tuple containing only valid diagnostic service ports, maintaining original order.

1.3. Code Expansion Walkthrough

Given three ports:

ServicePortType    p1;
NonServicePortType p2;
ServicePortType    p3;

auto ports = std::tie(p1, p2, p3);

When invoking auto filtered = onlyDiagnosticServicePorts(ports);, the compiler expands the function template as follows:

auto onlyDiagnosticServicePorts(
    const std::tuple<ServicePortType&, NonServicePortType&, ServicePortType&>& ports
) {
    return std::apply(
        // 1. Lambda expands parameters:
        [](ServicePortType& p1, NonServicePortType& p2, ServicePortType& p3) {
            // 2. Pack expansion expands tuple_cat arguments:
            return std::tuple_cat(
                getDiagnosticServicePortOrEmpty(p1), // Returns std::tuple<ServicePortType&>
                getDiagnosticServicePortOrEmpty(p2), // Returns std::tuple<>
                getDiagnosticServicePortOrEmpty(p3)  // Returns std::tuple<ServicePortType&>
            );
        },
        ports
    );
}

References & Useful Links

Published Apr 14, 2026

← Back to articles