Skip to content

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:

cpp
#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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
#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:

cpp
#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:

cpp
#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:

cpp
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:

cpp
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:

cpp
[this] { return x; }; // Captures *this by value

Example: 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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
#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:

cpp
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:

cpp
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:

cpp
#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:

cpp
#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:

cpp
#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:

cpp
#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:

cpp
#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:

cpp
#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:

cpp
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:

cpp
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:

cpp
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:

cpp
#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 int

Example: 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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
#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:

cpp
#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:

cpp
#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:

cpp
#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
#endif

Example: feature_test_macros.cpp

Released under the MIT License.