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.
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:
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.
- Working draft: the committee votes proposals, called papers, into the draft one by one.
- Committee Draft ballot: national standards bodies send comments, and the committee resolves them.
- Technical work complete: the feature set is frozen, and the final draft is N5050.
- Draft International Standard ballot: national bodies vote on the final text. This box has a double border and the note "C++26 is here".
- 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:
- 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.
- 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.
- 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.
- 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.
- 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++26mode compiles against a draft that each compiler implements in part. - Do not test
__cplusplusto detect C++26. The final draft sets it to202603L, but GCC 16.2 and Apple clang 21 both report202400in 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
Four changes lead C++26, and reflection is the largest
- Static reflection (papers P2996R13, P1306R5, P3394R4 and P3491R3):
^^Tturns a type, member or function into a value of typestd::meta::info; functions instd::metaquery it;[: :]splices it back into code;template forrepeats code for each result; and annotations attach constant values to declarations. - Contracts (P2900R14):
pre(…)andpost(…)on function declarations and thecontract_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
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 -:
// 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:
"""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:
"""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 onlystd::saturating_addand Apple’s onlystd::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++23builds, Apple clang already gives six of these macros their C++26 values, among them pack indexing and= deletewith 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::linalgor the senders ofstd::execution, and neither compiler accepts trivial unions. GCC 16.2 defines no__cpp_lib_simdon any target, and its<bits/version.h>header switches itsstd::simdon 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.
Checkpoint
Check: Feature-test macros as evidence
The big three need GCC 16.2 and the right flags
- GCC 16.2:
-std=c++26, plus-freflectionfor 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:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| 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
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.
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
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.
-
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?
-
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.
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::executionare 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
__cplusplusstill reads 202400 on both compilers. - GCC 16.2 builds reflection with
-std=c++26 -freflectionand 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
- The Standard (current ISO C++ standard and working drafts) (Standard C++ Foundation)
- Working Draft, Programming Languages: C++ (N5050) (ISO/IEC JTC1/SC22/WG21)
- N5051: Editors' Report for the C++ Working Draft (ISO/IEC JTC1/SC22/WG21)
- C++26 is done! Trip report: ISO C++ standards meeting in London (Croydon) (Herb Sutter (Sutter's Mill))
- Trip report: ISO C++ standards meeting in Brno, Czechia (Herb Sutter (Sutter's Mill))
- GCC 16 Release Series, Changes, New Features, and Fixes (GNU Project)
- Options Controlling C++ Dialect (GCC 16.2 manual) (GNU Project)
- C++ Support in Clang (LLVM Project)
- Clang Language Extensions: Language Extensions Back-ported to Previous Standards (LLVM Project)
- SD-6: SG10 Feature Test Recommendations (Standard C++ Foundation)
- P2996R13: Reflection for C++26 (ISO/IEC JTC1/SC22/WG21)
- P2900R14: Contracts for C++ (ISO/IEC JTC1/SC22/WG21)
- P2300R10: std::execution (ISO/IEC JTC1/SC22/WG21)
- P2795R5: Erroneous behaviour for uninitialized reads (ISO/IEC JTC1/SC22/WG21)
- P3471R4: Standard library hardening (ISO/IEC JTC1/SC22/WG21)
- P3097R3: Contracts for C++: virtual functions (ISO/IEC JTC1/SC22/WG21)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress