Nov 9, 2026

[C++] coding tricks/tips from talk "A Metaprogramming Checklist - Ben Deane - C++Now 2026"

Reference:
https://www.youtube.com/watch?v=6n_mOFP_kBA

Marking operator= as const breaks conventional value semantics, but serves three distinct, intentional architectural patterns:

  • Compile-Time DSLs & Tag Parameters: Allows constexpr or immutable global tag instances to produce configuration/option pairs without mutating the tag itself:


template <typename T>
struct SettingOption {
    const char* key;
    T value;
};

template <typename T>
struct SettingKey {
    const char* name;

    // operator= does NOT modify *this; it constructs and returns an option pair
    constexpr SettingOption<T> operator=(T val) const {
        return SettingOption<T>{name, val};
    }
};

// Global immutable tag
inline constexpr SettingKey<int> timeout{"timeout_ms"};
inline constexpr SettingKey<bool> verbose{"verbose"};

// Usage:
void configure(SettingOption<int> opt);

int main() {
    // 'timeout' is const/constexpr, but 'timeout = 1000' is valid
    configure(timeout = 1000);
}
  • Proxy References & Shallow-Const Semantics: For views, handles, or memory-mapped hardware abstractions, the proxy itself remains bitwise identical (its address/handle does not change), but writes propagate through to the underlying resource:
struct HardwareReg {
    volatile uint32_t* addr;
    void operator=(uint32_t val) const noexcept { *addr = val; }
};
  • Internally Synchronized / Thread-Safe Types: Under C++11 Core Guidelines, const denotes thread safety (safe to call concurrently without external locking). An assignment operating purely on internal atomics or mutable std::mutex states can qualify as const.(For ordinary value types, avoid operator= const—use lvalue ref-qualifiers (operator=(...) &) instead to guard against unintended temporary assignment).

Why Use requires true in C++20?

At first glance, requires true looks completely redundant.
However, in C++20, attaching any requires clause—even one that unconditionally evaluates to true—changes how the compiler handles a function during explicit template instantiation, overload resolution, and friend function generation. 

1. Skipping Member Functions During Explicit Class Instantiation
Normally, class template member functions are lazily instantiated only when called. However, explicitly instantiating a class template (e.g., template struct Wrapper;) forces the compiler to eagerly instantiate every unconstrained non-template member function body immediately. If a type T does not support an operation inside one of those helper functions, compilation fails even if that function is never called.

Under the C++20 standard ([temp.explicit]/8), explicit class instantiation ignores constrained member functions. Adding requires true keeps the member function lazy during explicit class instantiation without requiring a complex concept definition.
#include <iostream>

template <typename T>
struct Wrapper {
    T value;

    // Without 'requires true', 'template struct Wrapper<NoPrint>;' fails to compile
    // because NoPrint has no operator<<.
    void debug_print() requires true {
        std::cout << value << '\n';
    }
};

struct NoPrint {};

// Explicitly instantiate Wrapper<NoPrint>.
// Compiles cleanly ONLY because debug_print() is constrained with 'requires true'.
template struct Wrapper<NoPrint>;

int main() {
    Wrapper<NoPrint> w; // Valid as long as w.debug_print() is never called

    Wrapper<int> wi{42};
    wi.debug_print();   // Instantiated on demand; prints 42
}

2. Overriding an Unconstrained Template in Overload Resolution 
If a third-party library or legacy header provides an unconstrained function template, defining another function template with the exact same signature normally causes a redefinition error (if in the same scope) or an ambiguous call error. Under C++20 partial ordering rules ([temp.func.order]), a constrained template is strictly more specialized than an unconstrained template. Adding requires true allows a new generic implementation to cleanly beat the unconstrained baseline in overload resolution for all types T, without restricting which types are accepted.

#include <iostream>

// (1) Unconstrained baseline (e.g., from a legacy header)
template <typename T>
void serialize(const T&) {
    std::cout << "Unconstrained baseline\n";
}

// (2) Constrained with 'requires true'
// Beats (1) in overload resolution for every type T.
template <typename T>
requires true
void serialize(const T&) {
    std::cout << "Preferred generic implementation\n";
}

int main() {
    serialize(123);     // Calls (2)
    serialize("hello"); // Calls (2)
} 

3. Avoiding Redefinition Errors in Non-Dependent Hidden Friends 
When defining a non-template friend function inline inside a class template, if the friend's parameter types do not depend directly on the class template parameter T, the compiler treats the friend as an ordinary function. Instantiating the class template with two different types (e.g., Derived and Derived) will define the exact same function twice and fail with a redefinition error. Under C++20 ([temp.friend]/9), a constrained non-template friend function is uniquely tied to its enclosing class template specialization, giving each specialization its own distinct friend entity and preventing redefinition errors when multiple specializations are instantiated.

#include <iostream>

struct Base {};

template <typename T>
struct Derived : Base {
    // Without 'requires true', instantiating Derived<int> and Derived<double>
    // fails to compile with a redefinition error of 'void ping(const Base&)'.
    // With 'requires true', each specialization gets its own unique friend entity.
    friend void ping(const Base&) requires true {
        std::cout << "ping\n";
    }
};

int main() {
    Derived<int> d1;
    Derived<double> d2; // Compiles cleanly with no redefinition error

    // Note: Only works while a single specialization is instantiated in the TU;
    // once two exist, calling ping(d1) becomes ambiguous between Derived<int>::ping
    // and Derived<double>::ping because both take 'const Base&'.
} 
Use Case Without requires true With requires true
Explicit Class Instantiation
template struct Foo<T>;
Eagerly instantiates all member function bodies, failing if T lacks required operations. Skips instantiating the member function body until it is explicitly called ([temp.explicit]/11).
Overloading Identical Template Signatures Causes a redefinition or ambiguous overload error. Wins overload resolution over the unconstrained template via partial ordering ([temp.func.order]).
Non-Dependent Inline Friend in Class Template Causes a redefinition error when the class template is instantiated with multiple types. Ties the friend entity to the enclosing class specialization ([temp.friend]/9).

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.