Highlight
C++20 Concepts allows the error information of generic code to be transferred from inside the template to the calling site, and the compiler rejects unmatched types before instantiation; with constexpr compile-time evaluation, the runtime initialization overhead can also be advanced to the compilation stage.
Core Content
Template error reporting is a headache
When writing C++ templates, a common scenario: functionsisOddThe original intention was to only accept integers, but the test code was written by hand.1.1instead of11. The compiler error points to the inside of the template, saying%operator cannot be used withdouble. Developers have to rummage through layers of template expansion information to find the real error at the call site.
Before C++20, template parameters could only be constrained by comments, parameter naming conventions, or complexenable_if. These methods either have no compiler checks, or the error messages are still obscure.
Concepts: Intercept errors before instantiation
C++20 introduces Concepts, which allows the conditions that parameter types must meet to be specified when the template is declared. The compiler checks the concept before trying to instantiate the template, and if there is no match, an error will be reported directly at the calling site.
Detailed Content
Use standard library Concept to constrain templates
(03:56)
#include <concepts>
// Before: any type could be passed in
template <class T>
bool isOdd(T value) {
return value % 2 != 0;
}
// Now: only integer types are accepted
template <std::integral T>
bool isOdd(T value) {
return value % 2 != 0;
}
// Test code
isOdd(11); // Passes
isOdd(1.1); // Compiler error: double does not satisfy the integral concept
Key points:
std::integralis a concept provided by the C++ standard library and is included in<concepts>in header file- use
std::integralsubstituteclassKeyword, restricts the template to only accept built-in integer types - incoming
doubleWhen, the compiler directly reports an error inisOdd(1.1)this line instead of inside the template
Standard Library Concepts Overview
(06:04)
<concepts>The header files provide a core set of concepts:
#include <concepts>
// Type concepts
static_assert(std::floating_point<float>); // float satisfies it
static_assert(std::floating_point<double>); // double satisfies it
// Constructible/destructible
static_assert(std::destructible<int>);
static_assert(std::move_constructible<int>);
// Type conversion
static_assert(std::convertible_to<int, float>); // int can convert to float
// Comparable
static_assert(std::equality_comparable<int>); // int has the == operator
Key points:
floating_point: Test whether it is a floating point type -convertible_to: Test whether type A can be implicitly converted to type B -move_constructible: Test whether the move constructor can be constructed from values of the same type -equality_comparable: Test whether there is a valid==operator
requires clause: multiple conditions constraints
(08:22)
When a single concept is not enough, userequiresClauses combine multiple conditions:
#include <concepts>
template <typename T>
requires std::equality_comparable<T> && std::default_constructible<T>
bool isDefaultValue(T value) {
return value == T{}; // Requires == and default construction
}
Key points:
requiresclause is placed after the template parameter list- use
&&To combine multiple concepts, all conditions must be met at the same time - This ensures that the template is only instantiated if the type supports the required operation
Custom Concept: use requires expression
(13:46)
Suppose there is a graphics rendering library with various shapesgetDistanceFrommethod. can define aShape concept:
// Custom Shape concept
template <typename T>
concept Shape = requires(T shape) {
{ shape.getDistanceFrom(0.0f, 0.0f) } -> std::same_as<float>;
};
// Use a concept to constrain the template
template <Shape T>
Color computePixelColor(const T& shape, float x, float y) {
float distance = shape.getDistanceFrom(x, y);
return distance < 0 ? Color::white : Color::black;
}
// The Circle class satisfies the Shape concept
class Circle {
public:
float getDistanceFrom(float x, float y) const {
float dx = x - centerX;
float dy = y - centerY;
return std::sqrt(dx * dx + dy * dy) - radius;
}
private:
float centerX, centerY, radius;
};
Key points:
conceptKeywords define new concepts -requiresThe argument list in an expression declares the values to be tested- Expression requirements
{ ... }Test whether a certain piece of code compiles --> std::same_as<float>Check return value type with compound requirement - The expression is only verified at compile time and will not be actually executed.
Concept overloading: automatically select the best matching function
(18:20)
Functions with different concept constraints can form overloads, and the compiler automatically selects the most specific version:
// Base shape concept
template <typename T>
concept Shape = requires(T shape) {
{ shape.getDistanceFrom(0.0f, 0.0f) } -> std::same_as<float>;
};
// Gradient shape concept (also satisfies Shape)
template <typename T>
concept GradientShape = Shape<T> && requires(T shape) {
{ shape.getGradientColor(0.0f, 0.0f) } -> std::same_as<Color>;
};
// Base version: all shapes
template <Shape T>
Color computePixelColor(const T& shape, float x, float y) {
return shape.getDistanceFrom(x, y) < 0 ? Color::white : Color::black;
}
// Gradient version: shapes with gradients
template <GradientShape T>
Color computePixelColor(const T& shape, float x, float y) {
float dist = shape.getDistanceFrom(x, y);
return dist < 0 ? shape.getGradientColor(x, y) : Color::black;
}
// GradientCircle satisfies both Shape and GradientShape
// The compiler chooses the second, more specific overload
GradientCircle circle;
auto color = computePixelColor(circle, 1.0f, 2.0f);
Key points:
GradientShapeinheritShaperequirements to ensure that they are metGradientShapeThe type must satisfyShape- The compiler chooses the most specific version of the constraint during overload resolution- This is much simpler and more intuitive than the SFINAE technique
constexpr: compile-time evaluation reduces startup overhead
(21:09)
// Parse colors at compile time
constexpr Color fromHexCode(const char* hex) {
// Parse the #RRGGBB string
return Color{hexToInt(hex, 0), hexToInt(hex, 2), hexToInt(hex, 4)};
}
// Initialize the palette at compile time
constexpr Color colorPalette[] = {
fromHexCode("#FF0000"), // Compile-time evaluation
fromHexCode("#00FF00"),
fromHexCode("#0000FF")
};
Key points:
constexprMarked functions can be executed at compile time -constexprVariables are guaranteed to be initialized at compile time- Xcode 14 enhances the constexpr support of the standard library, and more types and algorithms can be used at compile time
- Compile-time initialization reduces runtime overhead when the app starts
Upgrade to C++20
(26:29)
Xcode 14 fully supports C++20. Change “C++ Language Dialect” in Build Settings to C++20 to use concepts and other features. C++20 has no minimum deployment target requirements and can be released against existing supported OS versions.
Core Takeaways
-
Add concept constraints to existing templates Find the commonly used function templates and class templates in the project, and use the standard library concept or custom concept to add type constraints. Error messages are immediately clear and maintenance costs are reduced.
-
Replace complex SFINAE techniques with concepts If there is in the code
enable_if、void_tWait for template metaprogramming techniques to be rewritten using concept. The code is more readable and compilation errors are easier to understand. -
Change constant initialization at startup to constexpr Check constant arrays, lookup tables, configuration data in the project. If the initialization logic can be done at compile time using constexpr, startup time will be reduced.
-
Define concept system for domain model For example, rendering engine definition
Shape、GradientShape、TexturedShapeetc concept. New types can be seamlessly connected as long as they meet the concept, and the interface document is the code.
Related Sessions
- What’s new in Xcode 14 — Xcode 14 fully supports C++20
- Demystify parallelization in Xcode builds — Build system optimization, C++ projects also benefit
- Improve app size and runtime performance — Compiler optimization, constexpr reduces runtime overhead
- Link fast: Improve build and launch times — Link optimization, C++ project build acceleration
Comments
GitHub Issues · utterances