Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 18 – C++26 previews: reflection, contracts and safety

What is in C++26 and which compilers support it

The headline C++26 features, from reflection and contracts to std::execution and safer defaults, and which of them GCC 16 and Apple clang 21 accept.

  • Advanced
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Summarise the headline language and library features of C++26
  • Check which C++26 features a compiler supports with feature-test macros
  • Choose the compiler and flags for each C++26 feature this module teaches
  • Read the preview labels used in the lessons of this module

Before you start

What you’ll learn · Before you start

What you will learn

  • Summarise the headline language and library features of C++26
  • Check which C++26 features a compiler supports with feature-test macros
  • Choose the compiler and flags for each C++26 feature this module teaches
  • Read the preview labels used in the lessons of this module

Before you start

On this page

Of the 26 C++26 feature-test macro: A macro such as __cpp_modules or __cpp_lib_modules whose value, a year and month such as 202207, says that a compiler or its library claims a feature. A toolchain may build a feature without defining its macro, or define the macro without the feature. More in “C++20 modules: export, import and partitions” surveyed below, GCC 16.2 defines 21 and Apple clang 21 only 8, so the same -std=c++26 flag accepts different code on this track’s two compilers. C++26 is the next edition of the ISO C++ standard, and its technical work is finished: the feature set is frozen, and the final draft, N5050, is the basis of the text national standards bodies vote on before ISO publishes it. Until then, C++23 stays the current standard.

Four changes lead the release: static reflection, which lets a program inspect its own types while it compiles; contracts, which put preconditions and postconditions on function declarations; std::execution, a standard model for asynchronous work; and safer defaults, with erroneous behaviour: C++26's name for reading an uninitialised local variable: the variable holds a value the implementation chooses, the program goes on predictably, and compilers are encouraged to diagnose the read. The code is still a bug. More in “Variables and initialisation: =, () and {}” for reads of uninitialised variables and a hardened standard library: A standard library that checks the preconditions of calls such as operator[] and front() while the program runs, and stops it when one fails, as libc++'s hardening modes and libstdc++ with _GLIBCXX_ASSERTIONS do. C++26 makes such hardened implementations part of the standard. More in “std::vector: the container to reach for first”. GCC 16 implements reflection (behind -freflection), contracts and erroneous behaviour; Apple clang 21 implements none of them. You will measure what each compiler accepts, choose flags for each feature and read this module’s preview labels.

C++26 is finished, but ISO has not published it

Each edition of C++ passes five stages on its way from the first proposal to ISO publication. Follow them from top to bottom; the box with the double border is where C++26 stands:

Five stages from working draft to ISO standard; C++26 is at stage 4, the Draft International Standard ballot.1. Working draftproposals (papers) arevoted in one by one2. Committee Draft ballotnational bodies comment;the committee resolves it3. Technical work completefeature set frozen;final draft N50504. Draft InternationalStandard ballot C++26 is here5. Published by ISOC++26 replaces C++23as the current standard

C++26 is technically finished and waits for its ISO ballot and publication

Text description of the diagram

The diagram shows five stages from top to bottom, each leading to the next.

  1. Working draft: the committee votes proposals, called papers, into the draft one by one.
  2. Committee Draft ballot: national standards bodies send comments, and the committee resolves them.
  3. Technical work complete: the feature set is frozen, and the final draft is N5050.
  4. Draft International Standard ballot: national bodies vote on the final text. This box has a double border and the note "C++26 is here".
  5. Published by ISO: C++26 replaces C++23 as the current standard.

Proposals, called papers, are voted into a The text of the next edition of C++ as the ISO C++ committee edits it: proposals, called papers, are voted into it one by one, and each revision is published under a number, such as N5050.; national bodies comment on a Committee Draft; the committee resolves the comments and freezes the text; the final text goes to a ballot as a The final text of an ISO standard, sent to the national standards bodies for a ballot once the technical work is complete. After the ballot, ISO publishes it as the new edition.; and ISO publishes it. C++26 is between the last two steps. The editors’ report N5051 describes N5050 as the final draft for C++26 and, at the same time, the first draft of C++29, and as the basis of the Draft International Standard.

Each name you meet in compiler flags and papers sits at one point on that path, and its place decides what you can rely on. Walk through them in order, from the current standard to the next draft:

Timeline · 5 events in order From C++23 to C++29, what each name means for your build Walk through the names in order. Each says what you can rely on at that stage, and what the two compilers of this track report.
  1. C++23

    The current standard published

    ISO published C++23 as ISO/IEC 14882:2024, and it stays the current standard until C++26 replaces it. -std=c++23 is the newest finished language mode.

  2. N5050

    C++26: technical work complete final draft

    The feature set is frozen. The editors' report N5051 calls N5050 the final draft for C++26 and the basis of the Draft International Standard. The draft sets __cplusplus to 202603L.

  3. DIS ballot

    Where C++26 stands: national bodies vote

    -std=c++26 compiles against this draft, which each compiler implements in part. In that mode GCC 16.2 and Apple clang 21 both report __cplusplus as 202400, not 202603, so test the feature-test macro of each feature instead.

  4. ISO publishes

    C++26 replaces C++23

    After the ballot, ISO publishes the text and C++26 becomes the current standard. The compilers still add its features release by release, so the feature tests of this lesson stay the way to check.

  5. C++29

    The next working draft has started not available yet

    N5050 is also the first working draft of C++29. Among its first changes are contracts on virtual functions (paper P3097R3), which C++26 does not allow. This module teaches only what is in the C++26 text.

The timeline has 5 events, from C++23 to C++29.

For daily work, the timeline comes down to three rules:

  • Build finished code as -std=c++23, the standard ISO published as ISO/IEC 14882:2024. A -std=c++26 mode compiles against a draft that each compiler implements in part.
  • Do not test __cplusplus to detect C++26. The final draft sets it to 202603L, but GCC 16.2 and Apple clang 21 both report 202400 in their C++26 modes, as the survey below shows. Test the feature-test macro of each feature you use instead.
  • Treat C++29 changes as not available. Its first changes include contracts on virtual functions (paper P3097R3), which C++26 does not allow, and this module teaches only what is in the C++26 text.

Checkpoint

Check: Where C++26 stands

  1. Question 1 of 2 Which -std= mode is the newest that compiles against a finished, published ISO standard?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: -std=c++23

    C++26's technical work is complete, but its text is still at the Draft International Standard ballot. Until ISO publishes it, C++23, ISO/IEC 14882:2024, is the current standard and -std=c++23 the newest finished mode.

  2. Question 2 of 2 A header switches on its C++26 code with #if __cplusplus >= 202603L. What happens when GCC 16.2 builds it with -std=c++26 -freflection?

    Choose one answer.

    A hint

    Look up what the survey printed for __cplusplus.

    Show the answer: Question 2 of 2

    Answer: The C++26 code is left out, because GCC 16.2 reports 202400 in that mode

    __cplusplus names the edition a mode aims at, and both compilers still report 202400 for their C++26 mode. A header that waits for 202603L treats GCC 16.2 as older than C++26 even where it accepts the feature, so test the feature-test macro of each feature instead.

Four changes lead C++26, and reflection is the largest

  • Static reflection (papers P2996R13, P1306R5, P3394R4 and P3491R3): ^^T turns a type, member or function into a value of type std::meta::info; functions in std::meta query it; [: :] splices it back into code; template for repeats code for each result; and annotations attach constant values to declarations.
  • Contracts (P2900R14): pre(…) and post(…) on function declarations and the contract_assert(…) statement, with the checking mode chosen when you build.
  • std::execution (P2300R10): senders and receivers, one vocabulary for describing asynchronous work, where it runs and how results and errors flow.
  • Erroneous behaviour (P2795R5): reading an uninitialised local variable gives an erroneous value instead of undefined behaviour, and implementations are encouraged to diagnose it.
  • Library hardening (P3471R4): a hardened implementation checks preconditions such as the index of vector::operator[], and a violation ends the program as a contract violation.

The five differ in what they ask of you. Three are new code to write, and the two safety changes act on the code you already have:

Change New code? What changes
Reflection Yes The names of members and enumerators come from the compiler, not from lists you keep
Contracts Yes A function’s assumptions sit in its declaration, and the build chooses how they are checked
std::execution Yes One vocabulary for where asynchronous work runs and how its results and errors flow
Erroneous behaviour No, a rebuild Reading an uninitialised local gives an erroneous value, not undefined behaviour
Library hardening No, a switch Calls such as vector::operator[] check their preconditions

Reflection is the largest addition. Until now, code that needed the names of a struct’s members or an enum’s enumerators had to repeat them by hand, in macros or in a code generator; with C++26 the compiler hands them over. Three lessons of this module build on it, from the operators up to generating code from annotated structs.

Contracts make the assumptions of a function part of its declaration, where callers, tools and the compiler can see them. Unlike assert, a contract’s checking mode is not a macro: a build chooses whether contracts are ignored, observed (reported, then execution continues) or enforced (reported, then the program stops).

std::execution and the two safety changes complete the list. The safety changes need no new code: recompiling as C++26 with a compiler that implements erroneous behaviour makes uninitialised reads well defined, and switching on a hardened library adds bounds checks to existing calls. No compiler in this track implements std::execution in its standard library yet, as the survey shows.

Key idea

Reflection, contracts and std::execution are new code to write; erroneous behaviour and library hardening make the code you already have safer, with a rebuild or a switch.

Most of C++26 is smaller features, and many already compile

Most of C++26 is smaller features, many of which both compilers already accept. Each fits in one line:

Feature What it lets you write
Pack indexing Ts...[0], the first type of a pack
Placeholder variables auto _ = lock(); twice in one scope
Deleted functions with a reason void pay(int) = delete("use paise as long long");
static_assert messages built in code A message that is any constant object with size() and data()
#embed #embed "logo.png", the file’s bytes, separated by commas
Structured binding packs auto [first, ...rest] = t; inside a template
constexpr exceptions throw and catch inside constant evaluation
Trivial unions A union with a std::string member that gets a trivial default constructor

The library gains std::inplace_vector (a vector with a fixed capacity and no heap), std::hive, std::simd, std::linalg, std::function_ref, std::copyable_function, std::indirect and std::polymorphic (value types that own an object on the heap), std::text_encoding, the <debugging> header, saturation arithmetic: Arithmetic that stops at the type's limits instead of overflowing: 100 + 100 in std::int8_t gives 127. C++26 adds it to <numeric> as std::saturating_add and its relatives, first named std::add_sat. More in “Integer overflow and signed/unsigned conversions”, and std::optional<T&>. The library half of the survey shows how unevenly these have arrived.

Checkpoint

Check: The four headline changes · The smaller additions

  1. Question 1 of 3 Which two headline changes of C++26 make existing code safer without any new code?

    Choose every answer that is right.

    Show the answer: Question 1 of 3

    Answer:

    • Library hardening
    • Erroneous behaviour

    Three of the headline changes need new code: reflection, contracts and std::execution. The two safety changes act on code that already exists, one through a rebuild as C++26 and the other through a hardened library.

  2. Question 2 of 3 Unlike assert, how is the checking mode of a C++26 contract chosen?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: When the program is built, which decides whether contracts are ignored, observed or enforced

    A build chooses whether contracts are ignored, observed (reported, then execution continues) or enforced (reported, then the program stops). assert depends on NDEBUG instead, which is easy to define or forget by accident.

  3. Question 3 of 3 Which C++26 feature lets this program, one of the second session's probes, compile?

    Read the code, then choose one answer.

    int main() {
      auto _ = 1;
      auto _ = 2;
    }
    Show the answer: Question 3 of 3

    Answer: Placeholder variables

    A placeholder variable named _ can be declared twice in one scope, which suits values such as the lock() guard of the table, which must live to the end of the scope but is never used by name. Both compilers of this track accepted the probe.

A feature-test macro is a claim, and a compile is the proof

A feature-test macro is the portable way to ask a compiler what it supports: the compiler predefines the language macros, and <version> defines the library ones. Each value is a date, year and month, that identifies the revision of the feature the implementation follows. This program prints 26 of them, plus __cplusplus; a macro that is not defined prints -:

feature_survey.cpp C++ · feature_survey.cpp
// Prints the C++26 feature-test macros that this compiler and its standard library define.
// Each value is the year and month of the paper the implementation follows; "-" means not defined.
#include <print>
#include <string_view>
#include <version>

// An undefined macro is left as it is, so after expansion its "value" is its own name.
#define TEXT_OF(m) #m
#define VALUE_OF(m) TEXT_OF(m)
#define REPORT(m) report(#m, VALUE_OF(m))

void report(std::string_view name, std::string_view value) {
    if (value == name) {
        value = "-";
    } else if (value.ends_with('L')) {
        value.remove_suffix(1);  // 202502L -> 202502
    }
    std::println("{} {}", name, value);
}

int main() {
    REPORT(__cplusplus);
    // Language features (predefined by the compiler)
    REPORT(__cpp_impl_reflection);
    REPORT(__cpp_expansion_statements);
    REPORT(__cpp_contracts);
    REPORT(__cpp_pack_indexing);
    REPORT(__cpp_placeholder_variables);
    REPORT(__cpp_deleted_function);
    REPORT(__cpp_static_assert);
    REPORT(__cpp_pp_embed);
    REPORT(__cpp_structured_bindings);
    REPORT(__cpp_constexpr_exceptions);
    REPORT(__cpp_variadic_friend);
    REPORT(__cpp_trivial_union);
    // Library features (defined by <version>)
    REPORT(__cpp_lib_reflection);
    REPORT(__cpp_lib_define_static);
    REPORT(__cpp_lib_contracts);
    REPORT(__cpp_lib_inplace_vector);
    REPORT(__cpp_lib_hive);
    REPORT(__cpp_lib_simd);
    REPORT(__cpp_lib_linalg);
    REPORT(__cpp_lib_senders);
    REPORT(__cpp_lib_function_ref);
    REPORT(__cpp_lib_indirect);
    REPORT(__cpp_lib_text_encoding);
    REPORT(__cpp_lib_debugging);
    REPORT(__cpp_lib_saturation_arithmetic);
    REPORT(__cpp_lib_optional);
}

The session builds it with both compilers of this track and prints their answers side by side, then builds it again with -std=c++23. Each line that starts with $ is a command, followed by what it printed:

26 feature-test macros on both compilers, in C++26 and C++23 mode Python · survey_session.py
"""Builds feature_survey.cpp with Apple clang and with GCC 16, runs both, and prints their answers side by side.

Then it builds the same survey with -std=c++23 and lists, for each compiler, the macros that already have their
C++26 value in that older mode.
"""

import os
import subprocess

# Homebrew's folders of commands (Apple silicon, then Intel Macs): the gcc formula puts g++-16 there.
env = dict(os.environ)
env["PATH"] = os.pathsep.join([env.get("PATH", ""), "/opt/homebrew/bin", "/usr/local/bin"])

builds = [
    ("Apple clang 21", "clang++ -std=c++26 -Wall -Wextra feature_survey.cpp -o survey-clang", "./survey-clang"),
    ("GCC 16.2", "g++-16 -std=c++26 -freflection -Wall -Wextra feature_survey.cpp -o survey-gcc", "./survey-gcc"),
]
older_builds = [
    ("Apple clang 21", "clang++ -std=c++23 -Wall -Wextra feature_survey.cpp -o survey-clang23", "./survey-clang23"),
    ("GCC 16.2", "g++-16 -std=c++23 -Wall -Wextra feature_survey.cpp -o survey-gcc23", "./survey-gcc23"),
]


def survey(build, program):
    """Builds and runs the survey, and returns its answers as {macro: value}."""
    print(f"$ {build}")
    subprocess.run(build, shell=True, env=env, check=True)
    output = subprocess.run(program, env=env, check=True, capture_output=True, text=True).stdout
    return dict(line.split(" ", 1) for line in output.splitlines())


columns = [survey(build, program) for _, build, program in builds]

names = list(columns[0])
print()
print(f"{'macro':<34}{builds[0][0]:>16}{builds[1][0]:>12}")
for name in names:
    print(f"{name:<34}{columns[0][name]:>16}{columns[1][name]:>12}")
both = sum(1 for n in names[1:] if columns[0][n] != "-" and columns[1][n] != "-")
gcc_only = sum(1 for n in names[1:] if columns[0][n] == "-" and columns[1][n] != "-")
clang_only = sum(1 for n in names[1:] if columns[0][n] != "-" and columns[1][n] == "-")
neither = sum(1 for n in names[1:] if columns[0][n] == "-" and columns[1][n] == "-")
print()
print(f"{len(names) - 1} feature macros: {both} defined by both, {gcc_only} by GCC only, {clang_only} by Apple clang only, {neither} by neither")

print()
older = [survey(build, program) for _, build, program in older_builds]
print()
print("With -std=c++23, the macros that already have their C++26 value:")
for (label, _, _), c26, c23 in zip(builds, columns, older):
    early = [n for n in names[1:] if c26[n] != "-" and c23[n] == c26[n]]
    print(f"  {label}: {len(early)} of {len(names) - 1}")
    for n in early:
        print(f"    {n} {c23[n]}")

Output

$ clang++ -std=c++26 -Wall -Wextra feature_survey.cpp -o survey-clang
$ g++-16 -std=c++26 -freflection -Wall -Wextra feature_survey.cpp -o survey-gcc

macro                               Apple clang 21    GCC 16.2
__cplusplus                                 202400      202400
__cpp_impl_reflection                            -      202603
__cpp_expansion_statements                       -      202506
__cpp_contracts                                  -      202502
__cpp_pack_indexing                         202311      202311
__cpp_placeholder_variables                 202306      202306
__cpp_deleted_function                      202403      202403
__cpp_static_assert                         202306      202306
__cpp_pp_embed                                   -      202502
__cpp_structured_bindings                   202411      202411
__cpp_constexpr_exceptions                       -      202411
__cpp_variadic_friend                       202403      202403
__cpp_trivial_union                              -           -
__cpp_lib_reflection                             -      202603
__cpp_lib_define_static                          -      202506
__cpp_lib_contracts                              -      202502
__cpp_lib_inplace_vector                         -      202603
__cpp_lib_hive                                   -           -
__cpp_lib_simd                                   -           -
__cpp_lib_linalg                                 -           -
__cpp_lib_senders                                -           -
__cpp_lib_function_ref                           -      202603
__cpp_lib_indirect                               -      202502
__cpp_lib_text_encoding                          -      202306
__cpp_lib_debugging                              -      202403
__cpp_lib_saturation_arithmetic             202311      202603
__cpp_lib_optional                          202506      202506

26 feature macros: 8 defined by both, 13 by GCC only, 0 by Apple clang only, 5 by neither

$ clang++ -std=c++23 -Wall -Wextra feature_survey.cpp -o survey-clang23
$ g++-16 -std=c++23 -Wall -Wextra feature_survey.cpp -o survey-gcc23

With -std=c++23, the macros that already have their C++26 value:
  Apple clang 21: 6 of 26
    __cpp_pack_indexing 202311
    __cpp_placeholder_variables 202306
    __cpp_deleted_function 202403
    __cpp_static_assert 202306
    __cpp_structured_bindings 202411
    __cpp_variadic_friend 202403
  GCC 16.2: 0 of 26

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 survey_session.py

A macro is only a claim, so the second session tests the claims: it writes one small program per feature and compiles each with both compilers. A feature counts as accepted when its program compiles without an error:

Thirteen C++26 features, compiled with both compilers Python · accepted_session.py
"""Compiles one small program per C++26 feature with Apple clang and with GCC 16, and prints which compiler accepts it.

A feature counts as accepted when the program compiles (-fsyntax-only) without an error; a warning is noted. An
unknown attribute is made an error, because a compiler that ignores [[indeterminate]] does not implement erroneous
behaviour. The thirteen programs are written to files first, so you can open any of them and see what was tried.
"""

import os
import subprocess
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path

env = dict(os.environ)
env["PATH"] = os.pathsep.join([env.get("PATH", ""), "/opt/homebrew/bin", "/usr/local/bin"])

COMPILERS = {
    "Apple clang 21": ["clang++", "-std=c++26", "-Werror=unknown-attributes"],
    "GCC 16.2": ["g++-16", "-std=c++26", "-freflection", "-Werror=attributes"],
}

FEATURES = {
    "reflection (^^)": "#include <meta>\nconstexpr std::meta::info r = ^^int;\nint main() {}\n",
    "expansion statements": "int main() {\n  int sum = 0;\n  template for (auto v : {1, 2, 3}) sum += v;\n  return sum - 6;\n}\n",
    "contracts": "int half(int x) pre(x % 2 == 0) { return x / 2; }\nint main() { contract_assert(half(4) == 2); }\n",
    "[[indeterminate]]": "int main() {\n  int buffer [[indeterminate]];\n  buffer = 1;\n  return buffer - 1;\n}\n",
    "pack indexing": "template <class... Ts> using first = Ts...[0];\nfirst<int, char> v = 0;\nint main() {}\n",
    "placeholder _": "int main() {\n  auto _ = 1;\n  auto _ = 2;\n}\n",
    "= delete(\"reason\")": "void pay(int paise) = delete(\"pass the amount as long long paise\");\nvoid pay(long long) {}\nint main() { pay(500LL); }\n",
    "#embed": "constexpr unsigned char logo[] = {\n#embed \"logo.bin\"\n};\nstatic_assert(sizeof logo == 4);\nint main() {}\n",
    "constexpr exceptions": "constexpr int parse() {\n  try { throw 7; } catch (int e) { return e; }\n}\nstatic_assert(parse() == 7);\nint main() {}\n",
    "trivial unions": "#include <string>\nunion Slot { std::string text; int number; };\nSlot slot;\nint main() {}\n",
    "std::inplace_vector": "#include <inplace_vector>\nint main() {\n  std::inplace_vector<int, 4> v{1, 2};\n  return v.size() - 2;\n}\n",
    "std::saturating_add": "#include <numeric>\nint main() { return std::saturating_add(200, 100) - 300; }\n",
    "std::add_sat": "#include <numeric>\nint main() { return std::add_sat(200, 100) - 300; }\n",
}

Path("logo.bin").write_bytes(bytes([0x89, 0x50, 0x4E, 0x47]))
files = []
for i, (feature, code) in enumerate(FEATURES.items(), start=1):
    path = Path(f"feature_{i:02}.cpp")
    path.write_text(code)
    files.append((feature, path))


def check(compiler, path):
    result = subprocess.run(COMPILERS[compiler] + ["-fsyntax-only", str(path)], env=env, capture_output=True, text=True)
    if result.returncode != 0:
        return "rejected"
    return "accepted, warns" if "warning" in result.stderr else "accepted"


with ThreadPoolExecutor(max_workers=4) as pool:
    jobs = {(feature, c): pool.submit(check, c, path) for feature, path in files for c in COMPILERS}
    results = {key: job.result() for key, job in jobs.items()}

for name, flags in COMPILERS.items():
    print(f"{name}: {' '.join(flags)} -fsyntax-only feature_NN.cpp")
print()
print(f"{'feature':<24}{'Apple clang 21':<18}GCC 16.2")
for feature, _ in files:
    print(f"{feature:<24}{results[(feature, 'Apple clang 21')]:<18}{results[(feature, 'GCC 16.2')]}")

Output

Apple clang 21: clang++ -std=c++26 -Werror=unknown-attributes -fsyntax-only feature_NN.cpp
GCC 16.2: g++-16 -std=c++26 -freflection -Werror=attributes -fsyntax-only feature_NN.cpp

feature                 Apple clang 21    GCC 16.2
reflection (^^)         rejected          accepted
expansion statements    rejected          accepted
contracts               rejected          accepted
[[indeterminate]]       rejected          accepted
pack indexing           accepted          accepted
placeholder _           accepted          accepted
= delete("reason")      accepted          accepted
#embed                  accepted, warns   accepted
constexpr exceptions    rejected          accepted
trivial unions          rejected          rejected
std::inplace_vector     rejected          accepted
std::saturating_add     rejected          accepted
std::add_sat            accepted          rejected

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 accepted_session.py

Four results in these two runs matter for real projects:

  • The big three are GCC-only. Reflection, expansion statements and contracts compile only with GCC 16.2, and reflection only with -freflection. Apple clang 21 does not even know the [[indeterminate]] attribute that comes with erroneous behaviour. Code that uses these features must either build with GCC alone or keep a fallback, which the reflection lessons show how to write.
  • Defined is not the same as compatible. Both libraries define __cpp_lib_saturation_arithmetic, but with different values, 202311 and 202603, because the two libraries follow different revisions of the feature. In this run GCC’s library accepts only std::saturating_add and Apple’s only std::add_sat, so a check of the macro alone does not make a call to either function portable. Compare the value as well.
  • Some macros appear early. In the -std=c++23 builds, Apple clang already gives six of these macros their C++26 values, among them pack indexing and = delete with a reason, which Clang’s documentation lists as C++26 features it also accepts in older modes. GCC 16.2 gives none of them its C++26 value before -std=c++26, so code that tests these macros takes its C++26 branch in an Apple clang C++23 build too.
  • Nobody has everything. Neither library provides std::hive, std::linalg or the senders of std::execution, and neither compiler accepts trivial unions. GCC 16.2 defines no __cpp_lib_simd on any target, and its <bits/version.h> header switches its std::simd on only for x86 targets with SSE2, so on this arm64 Mac <simd> declares nothing.

Key idea

A feature-test macro is a claim, and its value names a revision: test the macro of each feature you use, compare its value, and let a compiled program settle what the compiler really accepts.

Text Diff Paste the macro lists of two compilers, or of two versions of one compiler, to see which features differ.

Checkpoint

Check: Feature-test macros as evidence

  1. Question 1 of 2 Both standard libraries define __cpp_lib_saturation_arithmetic. What did the two sessions show about it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Different values, 202311 and 202603, and each library accepted a different function name

    A defined macro is not the same as a compatible interface. The value identifies the revision of the feature a library follows, and here the two revisions spell the addition differently, so a check of the macro alone does not make either call portable.

  2. Question 2 of 2 Apple clang 21 defines no __cpp_pp_embed in C++26 mode, yet the second session marks #embed "accepted, warns" on it. What does that show?

    Choose one answer.

    A hint

    Compare what each session asks the compiler.

    Show the answer: Question 2 of 2

    Answer: A compiler can compile a feature as an extension without claiming it with a macro

    A macro is a claim and a compiled program is the evidence, and they can disagree in both directions. Test the macro in code, and run a small program, as the second session does, before you rely on a feature.

The big three need GCC 16.2 and the right flags

  • GCC 16.2: -std=c++26, plus -freflection for reflection and -fcontract-evaluation-semantic= to choose how contracts are checked. It implements reflection, contracts and erroneous behaviour.
  • Apple clang 21: -std=c++26, or the older spelling -std=c++2c, and no extra flags for this module. It implements none of the three.

GCC 16’s release notes list reflection as enabled by -std=c++26 -freflection, together with annotations, the define_static_ functions, expansion statements, contracts and erroneous behaviour. The Clang status page lists reflection, contracts and erroneous behaviour as not implemented, and expansion statements as partial only from Clang 23; Apple clang 21 rejected all four probes in the sessions above. Re-run the two sessions when either compiler is updated: the tables are the evidence, and they change with every release.

Which build fits depends on your own code: whether it uses reflection or contracts, and whether Apple clang must build it too. Answer the three questions and watch the table rule builds out:

Interactive · answer and compare Which compilers and flags fit your code? Answer three questions about the code you want to build. The table marks the builds that fit and says why the others do not.
Does the code use reflection: ^^, splices or template for?
Does it use contracts: pre, post or contract_assert?
Must the same source also build with Apple clang 21?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Four ways to build C++26 code, against what the two sessions of this lesson recorded
Build Reflection, template for Contracts Smaller language features Builds with Apple clang 21
GCC 16.2 only, -freflection Compiles: every C++26 feature GCC 16.2 implements, with -std=c++26 -freflection Good compiled with -freflection; without it, std::meta is not declared Good pre, post and contract_assert compiled Good all of them except trivial unions, which neither compiler accepts Poor no: Apple clang 21 rejected reflection, template for and contracts
Both compilers, guarded Compiles: -std=c++26 on each, every C++26 feature behind its feature-test macro Mixed GCC builds it; the Apple clang build takes a fallback, such as a table of names Mixed GCC checks them; the Apple clang build needs another check Good pack indexing, the placeholder _ and delete("…") compiled on both Good yes: a feature it lacks waits behind its macro
Apple clang 21 only Compiles: the smaller language features it accepts with -std=c++26 Poor rejected: no ^^ and no template for Poor rejected: pre and post do not compile Mixed pack indexing, _, delete("…") and #embed with a warning; no constexpr exceptions Good yes
Both compilers, -std=c++23 Compiles: the finished standard and no C++26 feature Poor not in C++23: keep a hand-written list of names Poor not in C++23: assert or a check of your own Mixed Apple clang already gives six of their macros the C++26 value; GCC 16.2 only with -std=c++26 Good yes, on both compilers of this track
How the answers decide
  • “Does the code use reflection: ^^, splices or template for?” Yes, it uses reflection: rules out Apple clang 21 only and Both compilers, -std=c++23 and adds a caveat to Both compilers, guarded. Only GCC 16.2 with -freflection compiled ^^ and template for. A build on both compilers needs a fallback for Apple clang, which the reflection lessons of this module show.
  • “Does it use contracts: pre, post or contract_assert?” Yes, it uses contracts: rules out Apple clang 21 only and Both compilers, -std=c++23 and adds a caveat to Both compilers, guarded. Apple clang 21 rejected pre, post and contract_assert, and C++23 has no contracts, so the second build needs another way to check.
  • “Must the same source also build with Apple clang 21?” Yes, with both compilers: rules out GCC 16.2 only, -freflection. Apple clang 21 rejects reflection and contracts, so code that must build there keeps them behind feature-test macros.
  • “Must the same source also build with Apple clang 21?” No, GCC 16.2 is enough: adds a caveat to Both compilers, guarded. The guards and fallbacks are code to keep in step. They pay off only when a second compiler builds the same source.
  • “Does the code use reflection: ^^, splices or template for?” No reflection and “Does it use contracts: pre, post or contract_assert?” No contracts: adds a caveat to GCC 16.2 only, -freflection. Without reflection or contracts, -freflection adds nothing, and Apple clang 21 builds most of the smaller features too.

In short, GCC 16.2 with -std=c++26 -freflection builds everything this module teaches. Code that must also build with Apple clang 21 keeps each C++26 feature behind its feature-test macro, with a fallback for the compiler that lacks it.

Every lesson of this module starts with a preview label

Every lesson in this module begins with a label like the one below, so you know before reading whether you can run its examples:

Preview: GCC 16 only

Status: in the final C++26 draft. Examples: run with GCC 16.2, -std=c++26 -freflection. Apple clang 21: not supported. Elsewhere: use the fallback the lesson shows.

Each line of the label answers one question:

The question The label’s answer
Is the feature in C++26? Status: in the final C++26 draft
Which compiler ran the examples? Examples: GCC 16.2, with its flags
What do you do elsewhere? Use the fallback the lesson shows

Is the feature in C++26? Everything taught in this module is in the final draft; C++29 changes are named as such and never taught as available. Which compiler ran the examples? Each run record names the compiler and its flags, and a feature that only one compiler accepts is run on that compiler alone. What do you do elsewhere? Where a portable alternative exists, such as a table of enum names instead of reflection, the lesson shows it next to the C++26 version.

Checkpoint

Check: Compilers and flags for C++26 · The preview labels

  1. Question 1 of 2 A program that calls std::meta::enumerators_of builds with g++-16 -std=c++26 -freflection. Which of these builds of the same file fail?

    Choose every answer that is right.

    A hint

    Which flag switches reflection on, and which compiler has it?

    Show the answer: Question 1 of 2

    Answer:

    • clang++ -std=c++26 with Apple clang 21
    • g++-16 -std=c++26

    Reflection needs GCC 16.2 and both -std=c++26 and -freflection. Without the flag, <meta> declares nothing and the reflection macros __cpp_impl_reflection and __cpp_lib_reflection are not defined; Apple clang 21 has no reflection at all.

  2. Question 2 of 2 A lesson's label reads "Preview: GCC 16 only … Elsewhere: use the fallback the lesson shows". You have only Apple clang 21. What do you do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Read the recorded runs of its examples, and build the fallback the lesson shows

    A label answers three questions: is the feature in C++26, which compiler ran the examples, and what to do elsewhere. Elsewhere is the fallback the lesson shows next to the C++26 version.

Practise: features, macros and flags

Seventeen questions cover the lesson’s six ideas. Your weakest idea so far is asked about first, every reply is explained the moment you give it, and every claim about a compiler comes from the two sessions above.

Practice that adapts to you

Practise this lesson

17 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 17 At which stage of the ISO process is C++26?

    Choose one answer.

    Show the answer: Question 1 of 17

    Answer: The Draft International Standard ballot, after the technical work was completed

    C++26 is at stage 4 of 5: its technical work is complete, and the final draft N5050 is the basis of the Draft International Standard ballot. Publication by ISO is the last stage.

  2. Question 2 of 17 What did __cplusplus print in -std=c++26 mode on both compilers of the survey?

    Choose one answer.

    Show the answer: Question 2 of 17

    Answer: 202400

    Both printed 202400. The final draft N5050 sets __cplusplus to 202603L for C++26, so code that waits for that value treats both compilers as older than C++26. Test feature macros instead.

  3. Question 3 of 17 A colleague wants a precondition on a virtual function. Which edition of C++ allows that?

    Choose one answer.

    Show the answer: Question 3 of 17

    Answer: None yet; contracts on virtual functions are among the first changes for C++29

    N5050 is the final draft of C++26 and the first working draft of C++29, whose first changes include contracts on virtual functions. This module teaches only what is in the C++26 text and names C++29 changes as such.

  4. Question 4 of 17 Which headline change is the largest addition of C++26?

    Choose one answer.

    Show the answer: Question 4 of 17

    Answer: Static reflection

    Until C++26, code that needed the names of a struct's members or an enum's enumerators repeated them by hand, in macros or in a code generator. Reflection hands them over from the compiler.

  5. Question 5 of 17 A team rebuilds an old code base as C++26 with a compiler that implements erroneous behaviour, and changes no line. What does a read of an uninitialised local int now give?

    Choose one answer.

    Show the answer: Question 5 of 17

    Answer: An erroneous value, which the compiler is encouraged to diagnose, instead of undefined behaviour

    Erroneous behaviour turns a whole class of undefined behaviour into defined, diagnosable behaviour by a rebuild alone. That is why it is one of the two headline changes that need no new code.

  6. Question 6 of 17 What does #embed "logo.png" expand to?

    Choose one answer.

    Show the answer: Question 6 of 17

    Answer: The bytes of the file, separated by commas

    #embed "logo.png" expands to the file's bytes separated by commas, ready to initialise an array such as constexpr unsigned char logo[] = { … }.

  7. Question 7 of 17 Which C++26 library type is a vector with a fixed capacity and no heap?

    Choose one answer.

    Show the answer: Question 7 of 17

    Answer: std::inplace_vector

    std::inplace_vector<T, N> holds at most N elements inside the object itself. GCC 16.2 defines its macro; Apple clang 21's library does not have it yet.

  8. Question 8 of 17 Which of these C++26 library facilities had a feature-test macro in neither standard library?

    Choose one answer.

    Show the answer: Question 8 of 17

    Answer: std::hive

    Neither library provides std::hive, std::linalg or the senders of std::execution yet. GCC 16.2 defines no __cpp_lib_simd on any target, and it switches its std::simd on only for x86 targets with SSE2, so on the arm64 Mac of the survey <simd> declares nothing.

  9. Question 9 of 17 Which of these macros does the compiler predefine, rather than <version>?

    Choose one answer.

    Show the answer: Question 9 of 17

    Answer: __cpp_contracts

    __cpp_ macros name language features and come from the compiler; __cpp_lib_ macros name library features and come from <version>. Reflection has one of each.

  10. Question 10 of 17 In the recorded session, which of these C++26 features did Apple clang 21 compile without an error?

    Choose every answer that is right.

    Show the answer: Question 10 of 17

    Answer:

    • = delete with a reason
    • Placeholder variables named _
    • #embed
    • Pack indexing

    Apple clang 21 accepted pack indexing, placeholder variables, deleted functions with a reason and #embed. It rejected reflection, contracts, expansion statements and std::inplace_vector, which GCC 16.2 accepted.

  11. Question 11 of 17 Which of these did GCC 16.2 reject, even with -std=c++26 -freflection?

    Choose every answer that is right.

    Show the answer: Question 11 of 17

    Answer:

    • A union with a std::string member that is default-constructed (trivial unions)
    • std::add_sat

    GCC 16.2 rejected only the trivial union and std::add_sat. Its library spells saturation arithmetic std::saturating_add, while Apple's library spells it std::add_sat, and neither compiler implements trivial unions yet.

  12. Question 12 of 17 A library tests #if __cpp_lib_saturation_arithmetic and then calls std::add_sat. What happens on the two compilers of the survey?

    Choose one answer.

    Show the answer: Question 12 of 17

    Answer: It builds with Apple clang 21 and fails with GCC 16.2, which defines the macro but names the function std::saturating_add

    Both libraries define the macro, with the values 202311 (Apple clang) and 202603 (GCC), which identify different revisions of the feature. GCC 16.2 accepted only std::saturating_add and Apple clang 21 only std::add_sat, so a portable check compares the macro's value, not just whether it is defined.

  13. Question 13 of 17 Which other spelling does Apple clang 21 accept for its C++26 mode?

    Choose one answer.

    Show the answer: Question 13 of 17

    Answer: -std=c++2c

    Apple clang 21 takes -std=c++26 or the older -std=c++2c, and needs no other flag for the features of this module that it supports.

  14. Question 14 of 17 You compile a reflection example with g++-16 -std=c++26 and get "std::meta has not been declared". What is missing?

    Choose one answer.

    Show the answer: Question 14 of 17

    Answer: The -freflection flag

    GCC 16 implements reflection as an experimental feature that needs -freflection on top of -std=c++26. Without it the <meta> header declares nothing, and its feature macros __cpp_impl_reflection and __cpp_lib_reflection are not defined.

  15. Question 15 of 17 Your code uses reflection, and the same source must also build with Apple clang 21. Which plan fits?

    Choose one answer.

    Show the answer: Question 15 of 17

    Answer: GCC 16.2 with -std=c++26 -freflection, and the reflection code behind feature-test macros with a fallback for Apple clang

    The table of builds marks it: reflection rules out Apple clang alone and C++23, and the need for Apple clang rules out GCC alone, which leaves both compilers with every C++26 feature behind its macro.

  16. Question 16 of 17 Which three questions does the preview label at the top of each lesson of this module answer?

    Choose one answer.

    Show the answer: Question 16 of 17

    Answer: Is the feature in C++26, which compiler ran the examples, and what to do elsewhere

    Status: in the final C++26 draft. Examples: the compiler and flags that ran them. Elsewhere: the fallback the lesson shows, such as a table of enum names instead of reflection.

  17. Question 17 of 17 A lesson of this module mentions a change that C++29 adopted. How does it present it?

    Choose one answer.

    Show the answer: Question 17 of 17

    Answer: Named as a C++29 change, never taught as available

    The labels keep two promises apart: what C++26 contains, which the lessons teach and run, and what C++29 will change, which they only name.

Interview questions

Fresher · How do you check in code whether the compiler supports a C++26 feature?

Use the feature’s feature-test macro. Language features have macros that the compiler predefines, such as __cpp_contracts; library features have macros defined by <version> (or by the feature’s own header), such as __cpp_lib_inplace_vector. Test them with #if, ideally against the value you need, because the value is a date that identifies the revision implemented, and later revisions can change the interface: #if __cpp_lib_inplace_vector >= 202406L. Use __has_include(<inplace_vector>) only to ask whether a header exists, since a header can exist without the feature. Do not rely on __cplusplus: in C++26 mode, GCC 16.2 and Apple clang 21 report 202400, not the final value 202603. A good answer adds a fallback branch for compilers without the feature.

An interviewer who hears that answer usually probes three points: why the value matters, what __has_include adds, and why __cplusplus will not do. Choose the reply you would give to each; the feedback says what it gets right and what it leaves out:

Checkpoint

Interview round: three follow-up questions

  1. Question 1 of 3 The interviewer asks: why compare __cpp_lib_inplace_vector >= 202406L instead of only testing that the macro is defined?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: The value names the revision of the feature, and a later revision can change its interface

    The survey's saturation macro is the example: defined by both libraries, with two values for two revisions that spell the function differently. A good answer compares the value and keeps a fallback branch.

  2. Question 2 of 3 A follow-up: why is __has_include(<inplace_vector>) not enough on its own?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: It says only that the header exists, and a header can exist without the feature

    Use __has_include to ask whether a header can be included, and the feature-test macro, from <version>, to ask whether the feature is there and in which revision.

  3. Question 3 of 3 The interviewer pushes back: why not simply test __cplusplus >= 202603L?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: Both compilers report 202400 in C++26 mode, and one number cannot say which features a compiler has

    __cplusplus says which edition a mode aims at. Whether a compiler implements a particular feature of that edition is what its feature-test macro says, and only a compile proves it.

The module’s question bank holds two longer problems that start from this lesson: adopting reflection in public headers that two compilers build, and a feature-test macro whose two values stand for two interfaces. Each comes with a first step you can try:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Senior to staff

    Your library is built with Apple clang on macOS and with GCC 16 on Linux, and its public headers are included by other teams. A colleague wants to use C++26 reflection now to print enum names in those headers. What do you decide, and how do you keep every build working?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A header checks that __cpp_lib_saturation_arithmetic is defined and then calls std::add_sat. It builds with Apple clang 21 but fails with GCC 16.2, although GCC defines the macro too. Explain why, and write a check that builds on both.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • C++26 is technically complete; its final draft is N5050, and C++23, ISO/IEC 14882:2024, stays the current standard until ISO publishes C++26.
  • Reflection, contracts and std::execution are new code to write, while erroneous behaviour and library hardening make existing code safer with a rebuild or a switch.
  • Most of C++26 is smaller features, and both compilers already accept many of them.
  • A feature-test macro is a claim whose value names a revision: compare values, and compile a small program to be sure, because __cplusplus still reads 202400 on both compilers.
  • GCC 16.2 builds reflection with -std=c++26 -freflection and contracts with -std=c++26; Apple clang 21 builds neither, so code for both compilers guards them.
  • Every lesson of this module opens with a label that says whether the feature is in C++26, which compiler ran its examples and what to do elsewhere.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.