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.
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.
Normally, class template member functions are lazily instantiated only when called. However, explicitly instantiating a class template (e.g., template struct Wrapper
#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
}
#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)
}
#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 Instantiationtemplate 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.