100 / 163 · C++20 · 12 min
Traits adapt expressions; concepts check boundaries
The same generic algorithm can accept types whose fields and member functions differ, as long as traits provide a consistent operation. Use C++20 requires to catch errors at the interface, and distinguish the three layers of commitment: syntactic satisfaction, return type, and business meaning.
In this lesson
Advanced C++ Metaprogramming
Actually finished the two-page sample of §5.2.2 Concept traits provided by the author, and pages 405–408 of the bonus-chapter PDF (§9.3.2 ending and §9.3.3 More on the double wrapper technique). The bonus file is not a whole chapter; the range is not exaggerated based on the name. The rest of the main text was not obtained; the table of contents was only used to check structure, and the retail and Google Books pages have only metadata and were not treated as main text.
Edition, actual reading range, and original sources →First state what the algorithm actually needs
Suppose the interface only wants the record count of an object. One external type stores a public field; another computes it via a member function. If the algorithm directly assumes every type has a count member, it leaks one class's layout into a requirement on all types. Traits can compress this difference into an adaptation layer and uniformly provide a get(value) expression for the algorithm.
The example deliberately does not use inheritance to reshape the two types. The primary template is declared but not defined; only explicitly supported types provide an adaptation. An unknown type will not be accepted by default just because it happens to have a field of the same name. This suits unmodified external structures or interfaces that need a reviewed whitelist. If all types in the project already share the same interface, there is no need to add traits just to demonstrate templates.
Use requires to constrain the results observable by the caller
RecordCount checks whether count_traits<T>::get(value) can be called and requires the expression result to be exactly size_t. This verifies the interface shape at compile time; it does not construct an object and does not execute get. The algorithm count_of only accepts types that satisfy this condition, so errors concentrate at this boundary rather than being exposed inside nested templates.
Choosing same_as rather than convertible_to is a deliberate contractual trade-off: a signed integer can convert to size_t but may turn a negative number into a huge positive one. Even checking the exact return type still cannot prove the value is really a record count, still less that the read has no expensive side effects. Semantics must be guaranteed by the adaptation implementation and the calling convention; a concept is not a program verifier.
Let historical techniques help reading rather than add concealment
The author's sample emphasizes that traits specify syntax rather than a unique entity; similar writing can be implemented by a static function, construction, or conversion. This helps understand why old-style metaprogramming often has multiple wrapping layers, and also reminds us not to treat a function appearance as a guarantee of side effects. This example deliberately chooses a straightforward static function and does not use implicit conversion to perform resource release or other hidden actions.
The book's term concept traits belongs to classic C++ design practice, not the later C++20 concepts syntax. This lesson uses modern requires to express constraints; it is a new example written from the sample's ideas, not a claim that the original book introduced C++20. We keep compile-time assertions for two observable results and an unsupported type, showing successful adaptation and boundary rejection, without executing deliberately illegal code.
Pitfalls
- requires checks whether an expression is valid; it does not prove the result is non-negative, accurate, side-effect-free, or thread-safe.
- Do not use a catch-all trait to guess the meaning of a same-named field on an arbitrary type; identical names do not equal identical business contracts.
Run an example
Minimum C++20 · complete program · Download .cpp
#include <cassert>
#include <concepts>
#include <cstddef>
#include <iostream>
struct Snapshot { std::size_t rows; };
class Batch {
std::size_t rows_;
public:
explicit Batch(std::size_t rows) : rows_(rows) {}
std::size_t size() const { return rows_; }
};
template<class T> struct count_traits;
template<> struct count_traits<Snapshot> {
static std::size_t get(const Snapshot& v) { return v.rows; }
};
template<> struct count_traits<Batch> {
static std::size_t get(const Batch& v) { return v.size(); }
};
template<class T>
concept RecordCount = requires(const T& v) {
{ count_traits<T>::get(v) } -> std::same_as<std::size_t>;
};
template<RecordCount T>
std::size_t count_of(const T& v) { return count_traits<T>::get(v); }
int main() {
static_assert(RecordCount<Snapshot>);
static_assert(RecordCount<Batch>);
static_assert(!RecordCount<int>);
Snapshot a{3};
Batch b{5};
assert(count_of(a) == 3);
assert(count_of(b) == 5);
std::cout << count_of(a) + count_of(b) << "\n";
}
Compile locally
g++ -std=c++20 -Wall -Wextra -Wpedantic -pthread books-advanced-cpp-metaprogramming.cpp -o example && ./exampleExpected result
8
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
An external type stores the record count in an int; can one simply cast it to size_t and treat that as the adaptation result? How should negative values be handled?
Show a reference answer
A direct conversion can pass this example's concept, but a negative value will convert to a large unsigned value and break the semantics. The adaptation layer should first check for negatives and throw an agreed exception, or separately design a fallible query interface that returns optional<size_t> and adjust the concept accordingly. One cannot claim that same_as has proved the range is correct; if the business guarantees it cannot be negative, that precondition should also be made visible.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.