C++20 Core Language Concepts
This document covers new or evolved C++20 core language concepts, with simple explanations, real-world scenarios, and short code snippets. Full examples will be added in examples folder. Only additions or evolutions from C++98/C++03/C++11/C++14/C++17 are included.
Concepts (C++20 Addition)
Explanation: Define constraints on template parameters, improving type checking and error messages.
Real-World Scenario: Restrict a generic function to numeric types in a math library.
Snippet:
#include <concepts> template<std::integral T> void process(T t) {}Example: concepts.cpp
Constraints and Requires Clauses (C++20 Addition)
Explanation: Specify requirements for template parameters using requires clauses.
Real-World Scenario: Ensure a template function accepts only floating-point types in a physics engine.
Snippet:
template<typename T> requires std::floating_point<T> void compute(T t) {}Example: constraints_requires.cpp
Constrained Templates (C++20 Addition)
Explanation: Use concepts directly in template declarations for concise constraints.
Real-World Scenario: Define a generic container that requires comparable types for sorting.
Snippet:
template<std::totally_ordered T> class SortedContainer {};Example: constrained_templates.cpp
Abbreviated Function Templates (C++20 Addition)
Explanation: Use auto in function templates for simpler syntax.
Real-World Scenario: Write a generic function for a logging system without verbose templates.
Snippet:
auto process(auto x) { return x; }Example: abbreviated_function_templates.cpp
Three-Way Comparison Operator (Spaceship Operator, <=>) (C++20 Addition)
Explanation: Single operator for comparing objects, returning std::strong_ordering or similar.
Real-World Scenario: Simplify sorting of game entities by multiple criteria.
Snippet:
struct Item { int id; auto operator<=>(const Item& other) const { return id <=> other.id; } };Example: spaceship_operator.cpp
std::strong_ordering (C++20 Addition)
Explanation: Comparison result type for strict total order (e.g., integers).
Real-World Scenario: Compare database keys ensuring strict ordering in a query system.
Snippet:
#include <compare> std::strong_ordering result = 5 <=> 10;Example: strong_ordering.cpp
std::weak_ordering (C++20 Addition)
Explanation: Comparison result type for weak ordering (e.g., case-insensitive strings).
Real-World Scenario: Sort usernames case-insensitively in a user management system.
Snippet:
#include <compare> std::weak_ordering result = str1 <=> str2;Example: weak_ordering.cpp
std::partial_ordering (C++20 Addition)
Explanation: Comparison result type for partial ordering (e.g., floating-point with NaN).
Real-World Scenario: Compare measurements with NaN in a scientific application.
Snippet:
#include <compare> std::partial_ordering result = 1.0 <=> std::numeric_limits<double>::quiet_NaN();Example: partial_ordering.cpp
Lambda Improvements (C++20 Evolution)
Explanation: Lambdas support constexpr, explicit template parameters, and capture of this by value.
Real-World Scenario: Use a lambda in a compile-time context for a math library.
Snippet:
constexpr auto lambda = []<typename T>(T x) { return x * x; };Example: lambda_enhancements.cpp
Stateless Lambda Call Operator as consteval (C++20 Addition)
Explanation: Stateless lambdas can have consteval call operators for compile-time execution.
Real-World Scenario: Generate compile-time constants in a configuration system.
Snippet:
auto lambda = []() consteval { return 42; };Example: consteval_lambda.cpp
Lambda Capture of [this] by Value (C++20 Evolution)
Explanation: Lambdas can capture this by value, copying the object.
Real-World Scenario: Capture a game object’s state in a callback without lifetime issues.
Snippet:
[this] { return x; }; // Captures *this by valueExample: lambda_this_capture.cpp
Template Lambdas (C++20 Addition)
Explanation: Lambdas with explicit template parameters for generic programming.
Real-World Scenario: Write a generic lambda for type-safe processing in a data pipeline.
Snippet:
auto lambda = []<typename T>(T x) { return x; };Example: template_lambdas.cpp
Constexpr Improvements (C++20 Evolution)
Explanation: constexpr supports virtual functions, dynamic memory, and STL types like std::string.
Real-World Scenario: Compute complex data structures at compile time in a math library.
Snippet:
constexpr int compute() { std::string s = "test"; return s.size(); }Example: constexpr_improvements.cpp
Constexpr Virtual Functions (C++20 Addition)
Explanation: Virtual functions can be constexpr, enabling compile-time polymorphism.
Real-World Scenario: Evaluate polymorphic behavior at compile time in a graphics engine.
Snippet:
struct Base { virtual constexpr int f() const { return 0; } };Example: constexpr_virtual.cpp
Constexpr Dynamic Memory Allocation (C++20 Addition)
Explanation: new and delete can be used in constexpr contexts for dynamic allocation.
Real-World Scenario: Allocate compile-time arrays in a configuration system.
Snippet:
constexpr int* alloc() { return new int[5]; }Example: constexpr_allocation.cpp
Constexpr std::string (C++20 Evolution)
Explanation: std::string supports constexpr operations for compile-time string manipulation.
Real-World Scenario: Generate constant strings at compile time for a parser.
Snippet:
constexpr std::string s = "hello"; constexpr auto len = s.size();Example: constexpr_string.cpp
Constexpr std::vector (C++20 Evolution)
Explanation: std::vector supports constexpr operations for compile-time container manipulation.
Real-World Scenario: Create compile-time lookup tables in a data processor.
Snippet:
constexpr std::vector<int> vec = {1, 2, 3};Example: constexpr_vector.cpp
Immediate Functions (consteval) (C++20 Addition)
Explanation: Functions guaranteed to evaluate at compile time.
Real-World Scenario: Generate compile-time constants for a configuration system.
Snippet:
consteval int getConfig() { return 42; }Example: immediate_functions.cpp
Constant Evaluation for Non-Type Template Parameters (C++20 Addition)
Explanation: Non-type template parameters can be evaluated at compile time with constexpr.
Real-World Scenario: Use compile-time strings as template parameters in a serializer.
Snippet:
template<auto Val> struct S {}; S<42> s;Example: consteval_nttp.cpp
std::is_constant_evaluated (C++20 Addition)
Explanation: Checks if code is evaluated in a constexpr context.
Real-World Scenario: Optimize code paths for compile-time vs. runtime in a library.
Snippet:
#include <type_traits> if (std::is_constant_evaluated()) {}Example: is_constant_evaluated.cpp
Modules (C++20 Addition)
Explanation: Replace headers with modules for faster compilation and better encapsulation.
Real-World Scenario: Modularize a game engine’s math library for cleaner dependencies.
Snippet:
export module math; export int add(int a, int b) { return a + b; }Example: modules.cpp
Import/Export Declarations (C++20 Addition)
Explanation: import and export manage module dependencies and interfaces.
Real-World Scenario: Import a utility module in a game engine’s core.
Snippet:
import math; int x = add(1, 2);Example: import_export.cpp
Coroutines (C++20 Addition)
Explanation: Functions that can suspend and resume execution, enabling asynchronous programming.
Real-World Scenario: Implement a non-blocking task scheduler in a server application.
Snippet:
#include <coroutine> std::suspend_always yield() { return {}; }Example: coroutines.cpp
co_await (C++20 Addition)
Explanation: Suspends a coroutine until an awaited operation completes.
Real-World Scenario: Wait for an async I/O operation in a network server.
Snippet:
#include <coroutine> auto task() { co_await std::suspend_always{}; }Example: co_await.cpp
co_yield (C++20 Addition)
Explanation: Yields a value from a coroutine, suspending execution.
Real-World Scenario: Generate a sequence of game events incrementally.
Snippet:
#include <coroutine> auto gen() { co_yield 42; }Example: co_yield.cpp
co_return (C++20 Addition)
Explanation: Returns a value from a coroutine, completing it.
Real-World Scenario: Return the result of an async computation in a server.
Snippet:
#include <coroutine> auto task() { co_return 42; }Example: co_return.cpp
std::source_location (C++20 Addition)
Explanation: Provides compile-time information about the source code (e.g., file, line).
Real-World Scenario: Log the source location of errors in a debugging system.
Snippet:
#include <source_location> auto loc = std::source_location::current();Example: source_location.cpp
std::endian (C++20 Addition)
Explanation: Queries the endianness of the platform.
Real-World Scenario: Handle endianness in a cross-platform network protocol.
Snippet:
#include <bit> auto is_big = std::endian::native == std::endian::big;Example: endian.cpp
Designated Initializers (C++20 Addition)
Explanation: Initialize struct members by name, improving readability.
Real-World Scenario: Initialize a game character’s attributes clearly.
Snippet:
struct Player { int health; int score; }; Player p = { .health = 100, .score = 0 };Example: designated_initializers.cpp
[[no_unique_address]] Attribute (C++20 Addition)
Explanation: Allows empty non-static data members to occupy no space, optimizing memory.
Real-World Scenario: Optimize memory in a tightly packed data structure for a game.
Snippet:
struct Empty {}; struct Data { [[no_unique_address]] Empty e; int x; };Example: no_unique_address.cpp
constinit (C++20 Addition)
Explanation: Ensures static variables are initialized at compile time, avoiding runtime overhead.
Real-World Scenario: Initialize a global configuration constant in a system.
Snippet:
constinit int globalConfig = 42;Example: constinit.cpp
Conditional explicit (explicit(bool)) (C++20 Addition)
Explanation: The explicit specifier can now accept a boolean expression, enabling constructors and conversion operators to be conditionally explicit based on compile-time conditions. This is essential for wrapper types that should be implicitly convertible only when their underlying type supports implicit conversion.
Real-World Scenario: Build a generic Optional<T> wrapper that is implicitly constructible from T only when T itself is implicitly convertible from the source type, preventing accidental narrowing conversions in a type-safe API.
Snippet:
#include <type_traits>
template<typename T>
struct Wrapper {
T value;
template<typename U>
explicit(!std::is_convertible_v<U, T>)
Wrapper(U&& u) : value(std::forward<U>(u)) {}
};
// Wrapper<int> w = 3.14; // OK: double is convertible to int
// Wrapper<int> w2 = "hi"; // Error: const char* not convertible to intExample: conditional_explicit.cpp
Aggregate Initialization with Parentheses (C++20 Evolution)
Explanation: Aggregates can be initialized with parentheses, not just braces.
Real-World Scenario: Initialize a struct in a game with a constructor-like syntax.
Snippet:
struct Point { int x, y; }; Point p(1, 2);Example: aggregate_initialization.cpp
using enum (C++20 Addition)
Explanation: Imports enum values into the current scope, avoiding qualification.
Real-World Scenario: Simplify state handling in a game’s state machine.
Snippet:
enum class State { Idle, Running }; using enum State; auto s = Idle;Example: using_enum.cpp
Class Template Argument Deduction Enhancements (C++20 Evolution)
Explanation: Improved deduction for aggregates and aliases, simplifying instantiation.
Real-World Scenario: Create a pair in a data processor without specifying types.
Snippet:
std::pair p = {1, "value"}; // Deduced as std::pair<int, std::string>Example: ctad_enhancements.cpp
[[likely]] and [[unlikely]] Attributes (C++20 Addition)
Explanation: Hints to the compiler about branch prediction optimization.
Real-World Scenario: Optimize performance in a hot path of a game loop.
Snippet:
if (condition) [[likely]] {}Example: likely_unlikely.cpp
char8_t (C++20 Addition)
Explanation: A new fundamental type specifically for UTF-8 encoded characters, distinct from char and unsigned char. This provides type safety for UTF-8 strings and prevents accidental mixing of character encodings. The type u8string and u8string_view use char8_t as their character type.
Real-World Scenario: Enforce correct UTF-8 handling in an internationalized application where mixing encodings would cause display corruption.
Snippet:
#include <string>
char8_t c = u8'A';
std::u8string greeting = u8"Hello, World!";
// const char* p = u8"test"; // Error in C++20: u8 literals are char8_t[], not char[]
// Use char8_t to ensure UTF-8 encoding at the type level
std::u8string load_utf8_file(const std::filesystem::path& path);Example: char8_t.cpp
Non-Type Template Parameters of Class Type (C++20 Addition)
Explanation: Non-type template parameters (NTTPs) can now be of literal class type with public members, enabling compile-time strings, fixed-point numbers, and other structured values as template arguments. The class must have all public, non-mutable members and a defaulted spaceship operator.
Real-World Scenario: Create a compile-time string template parameter for a logging framework that selects log channels at compile time, enabling zero-overhead channel selection.
Snippet:
#include <algorithm>
#include <cstddef>
template<std::size_t N>
struct FixedString {
char data[N]{};
constexpr FixedString(const char (&str)[N]) {
std::copy_n(str, N, data);
}
auto operator<=>(const FixedString&) const = default;
};
template<FixedString Channel>
void log(const char* msg) {
// Channel is a compile-time string
}
// Usage: log<"network">("Connection established");
// log<"audio">("Sound loaded");Example: class_nttp.cpp
Destroying Delete (C++20 Addition)
Explanation: A new form of operator delete that receives the object before its destructor runs, giving the delete operator full control over destruction. Declared by adding a std::destroying_delete_t tag parameter. This enables advanced memory management patterns such as classes that store their size alongside the object.
Real-World Scenario: Implement a variable-length object (like a string with inline storage) where the deallocation size depends on runtime data stored in the object itself, which would normally be destroyed before operator delete sees it.
Snippet:
#include <new>
#include <cstddef>
struct VariableBuffer {
std::size_t size;
char data[]; // flexible array member (compiler extension)
void operator delete(VariableBuffer* ptr, std::destroying_delete_t) {
std::size_t total = sizeof(VariableBuffer) + ptr->size;
ptr->~VariableBuffer();
::operator delete(ptr, total);
}
static VariableBuffer* create(std::size_t n) {
void* mem = ::operator new(sizeof(VariableBuffer) + n);
auto* buf = new (mem) VariableBuffer{n, {}};
return buf;
}
};Example: destroying_delete.cpp
Feature Test Macros (C++20 Addition)
Explanation: Standardized preprocessor macros that allow code to detect at compile time which C++ language and library features are available. Each feature has a corresponding macro (e.g., __cpp_concepts, __cpp_modules) whose value indicates the version of the feature supported. This replaces ad-hoc compiler-version checks with a portable, feature-based approach.
Real-World Scenario: Write a cross-compiler library that uses C++20 concepts when available but falls back to SFINAE on older compilers, ensuring broad compatibility without sacrificing modern ergonomics.
Snippet:
#include <version> // guaranteed to define all library feature-test macros
#if __cpp_concepts >= 202002L
template<std::integral T>
T safe_add(T a, T b) { return a + b; }
#else
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
T safe_add(T a, T b) { return a + b; }
#endif
#ifdef __cpp_lib_format
#include <format>
// use std::format
#else
// fallback to printf or fmt library
#endifExample: feature_test_macros.cpp