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

Smaller C++26 language features you can use now

Pack indexing, the _ placeholder, = delete with a reason, computed static_assert messages, variadic friends and #embed, on Clang 21 and GCC 16.

  • Advanced
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: Eight features run in one program, on both compilers · Reasons and placeholders first, the rest where they fit · Your own words in the compiler's error · Practise: the smaller language features. With a pass: 2 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Use pack indexing, placeholder variables and deleted functions that give a reason
  • Write static_assert messages computed at compile time, and declare a pack of friends
  • Compile a file's bytes into a program with
  • Choose which of the smaller features a code base moving to C++26 adopts first
  • Predict what breaks when a C++23 code base switches to -std=c++26, and guard new features with their macros

Before you start

What you’ll learn · Before you start

What you will learn

  • Use pack indexing, placeholder variables and deleted functions that give a reason
  • Write static_assert messages computed at compile time, and declare a pack of friends
  • Compile a file's bytes into a program with
  • Choose which of the smaller features a code base moving to C++26 adopts first
  • Predict what breaks when a C++23 code base switches to -std=c++26, and guard new features with their macros

Before you start

On this page

set_timeout_ms(0.25), meant as a quarter of a second, compiles in C++23 and sets a timeout of 0 ms, because the double converts to long without a word. In C++26 one declaration turns that call into a compile error that quotes a reason you wrote. It is one of a handful of small language features that need no new library and that Apple clang 21 and GCC 16 both accept today with -std=c++26.

Pack indexing, Ts...[N], picks the N-th type or value of a pack. Several variables may be called _ in one scope, for values you must name but never read. = delete("reason") makes the compiler show your reason to whoever calls a deleted function. A static_assert message may be computed at compile time. friend Ts...; makes every type of a pack a friend. And #embed turns the bytes of a file into a list of numbers in the source. Each one replaces a workaround: a recursive template, a (void) cast, a comment next to a deleted overload, a fixed message, one friend line per class, a generated array.

Eight features run in one program, on both compilers

All eight in one file, built by both compilers with every warning they offer:

features.cpp C++ · features.cpp
// Eight smaller C++26 language features in one program; both compilers accept all of them in C++26 mode.
#include <cstdio>
#include <map>
#include <string>
#include <string_view>
#include <utility>

// 1. Pack indexing: Ts...[N] is the N-th type of a pack, and args...[N] the N-th value.
template <class... Ts> using first_t = Ts...[0];
template <class... Args> auto last(Args... args) { return args...[sizeof...(args) - 1]; }

// 2. A deleted function with a reason: the compiler shows the reason to anyone who calls it.
void set_timeout_ms(long ms) { std::printf("timeout: %ld ms\n", ms); }
void set_timeout_ms(double) = delete("pass whole milliseconds as a long, not seconds as a double");

// 3. A static_assert message computed at compile time: any constant with size() and data() will do.
struct Message {
  std::string_view text;
  constexpr std::size_t size() const { return text.size(); }
  constexpr const char* data() const { return text.data(); }
};
constexpr std::size_t buffer_size = 64;
static_assert(buffer_size >= 16, Message{"the buffer must hold at least one 16-byte record"});

// 4. Variadic friends: every type of the pack is a friend.
template <class... Readers> class Vault {
  friend Readers...;
  int secret = 42;
};
struct Auditor {
  static int peek(const Vault<Auditor>& v) { return v.secret; }
};

// 5. A structured binding as a condition: the object is tested, then its parts are named.
struct Parsed {
  int value;
  bool ok;
  explicit operator bool() const { return ok; }
};
Parsed parse_digit(char c) { return c >= '0' && c <= '9' ? Parsed{c - '0', true} : Parsed{0, false}; }

// 8. #embed: the bytes of a file as a list of numbers, here the three bytes of status.txt ("ok" and a newline).
constexpr unsigned char status_bytes[] = {
#embed "status.txt"
};

int main() {
  first_t<int, double> n = 3;
  std::printf("first_t is int: %d, last(1, 2.5, 'x') = %c\n", n, last(1, 2.5, 'x'));
  set_timeout_ms(250L);
  std::printf("vault secret: %d\n", Auditor::peek(Vault<Auditor>{}));
  if (auto [value, ok] = parse_digit('7')) std::printf("parsed %d (ok = %d)\n", value, ok);

  // 6. Placeholder variables: several variables named _ in one scope, for values you must name but never use.
  std::map<std::string, int> stock{{"pens", 12}, {"pads", 4}};
  int total = 0;
  for (const auto& [_, count] : stock) total += count;
  auto [_, inserted] = stock.insert({"ink", 7});
  std::printf("total %d, inserted ink: %d\n", total, inserted);

  // 7. An attribute on one structured binding.
  auto [count, label [[maybe_unused]]] = std::pair{3, "unused"};
  std::printf("count %d\n", count);
  std::printf("status.txt: %zu bytes, starting with '%c'\n", sizeof status_bytes, status_bytes[0]);
}
Both compilers in C++26 mode, with -Wall -Wextra -pedantic Python · features_session.py
from session import run

# Apple clang 21 and GCC 16 in C++26 mode, with the warnings a careful project uses.
run("""
clang++ -std=c++26 -Wall -Wextra -pedantic features.cpp -o features && ./features
g++-16 -std=c++26 -Wall -Wextra -pedantic features.cpp -o features-gcc && ./features-gcc
""")

Output

$ clang++ -std=c++26 -Wall -Wextra -pedantic features.cpp -o features && ./features
features.cpp:44:2: warning: #embed is a Clang extension [-Wc23-extensions]
   44 | #embed "status.txt"
      |  ^
1 warning generated.
first_t is int: 3, last(1, 2.5, 'x') = x
timeout: 250 ms
vault secret: 42
parsed 7 (ok = 1)
total 16, inserted ink: 1
count 3
status.txt: 3 bytes, starting with 'o'
$ g++-16 -std=c++26 -Wall -Wextra -pedantic features.cpp -o features-gcc && ./features-gcc
first_t is int: 3, last(1, 2.5, 'x') = x
timeout: 250 ms
vault secret: 42
parsed 7 (ok = 1)
total 16, inserted ink: 1
count 3
status.txt: 3 bytes, starting with 'o'

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

Both builds printed the same seven lines. first_t<int, double> is int, and last returned the last argument by indexing the value pack. The deleted overload never ran, because nothing called it, and the Auditor could read the vault’s private member only because the pack of friends named it. The if ran because parse_digit('7') returned an object whose explicit operator bool was true, and the loop over the map added 12 and 4 without naming the keys. The only diagnostic was Clang’s note that #embed is a Clang extension: Clang implements the directive, which C23 has too, but its status page does not list it as done for C++26. GCC accepts it as standard C++26.

Preview: both compilers

Part of the final C++26 draft and accepted today by both compilers of this track: each example ran on Apple clang 21 and on GCC 16.2 with -std=c++26, and where they differ, the run shows it.

The diagram follows last(1, 2.5, 'x'): one parameter pack: A template parameter or function parameter that stands for any number of arguments, none included: typename... Ts declares a pack of types, Ts... args a pack of function parameters, and sizeof...(Ts) counts the elements. More in “Variadic templates and fold expressions” of types and one of values, each indexed from 0:

The call last(1, 2.5, x) makes the packs Args = int, double, char and args = 1, 2.5, x; index 2 picks char and x.last(1, 2.5, 'x')instantiates Args = {int, double, char}the type pack Argsthe value pack argsargs...[sizeof...(args) - 1]is 'x', of type Args...[2], charArgs...[0]intArgs...[1]doubleArgs...[2]charargs...[0]1args...[1]2.5args...[2]'x'one value per type

Pack indexing picks the N-th type or value of a pack, counting from 0

Text description of the diagram

The diagram follows the call last(1, 2.5, 'x') from top to bottom.

  1. The call instantiates the function template with the type pack Args = {int, double, char}.
  2. The type pack, in three boxes: Args...[0] is int, Args...[1] is double and Args...[2] is char.
  3. An arrow labelled "one value per type" leads to the value pack, in three boxes: args...[0] is 1, args...[1] is 2.5 and args...[2] is 'x'.
  4. The last box shows the expression the function returns, args...[sizeof...(args) - 1]: the value 'x', whose type is Args...[2], char.

Each feature replaces a workaround that C++23 code still carries:

  • pack indexing: The C++26 syntax Ts...[i] for the type at position i of a type pack, and args...[i] for the argument at that position, where i is a constant expression counted from 0. More in “Variadic templates and fold expressions” names the N-th element of a pack directly, where C++23 needed a recursive template or std::tuple_element_t<N, std::tuple<Ts...>>.
  • The placeholder _ may be declared again in the same block, so a lock guard, an ignored binding and an unused result can all be called _.
  • = delete("reason") puts the reason into the error message itself, so the caller no longer has to find the comment beside the declaration.
  • A computed static_assert message can include the values that broke the assertion, such as the size that was too small.
  • Variadic friends declare a whole pack of friends in one line, so a template can open its private members to the types it is given.
  • A structured binding: A declaration such as auto [name, marks] = s; (C++17) that gives one name to each element of an array, a tuple-like type or a class whose data members are all public. The names refer to the members of one unnamed variable made from the initialiser: a copy with auto, s itself with auto&. More in “std::pair, std::tuple and structured bindings” as a condition tests the object, through its explicit operator bool, and names its parts in the same if.
  • An attribute on one binding, such as [[maybe_unused]], marks that binding alone, not the whole declaration.
  • #embed turns a file’s bytes into a comma-separated list at compile time, without a generated source file; the preprocessor lesson shows it in a longer example.

Checkpoint

Check: The eight features and what they replace

  1. Question 1 of 2 Which type is second_t<int, double, char>?

    Read the code, then choose one answer.

    template <class... Ts> using second_t = Ts...[1];
    Show the answer: Question 1 of 2

    Answer: double

    Pack indexing counts from 0, so Ts...[1] is the second type of the pack, double, as first_t<int, double> was int in features.cpp.

  2. Question 2 of 2 In one function body, which of these may all be named _ in C++26?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A lock guard, a structured binding you ignore and an unused result

    C++26 lets several local variables and structured bindings in one block share the name _, for values that must be named but are never read, as features.cpp did when it looped over the map without naming the keys.

Reasons and placeholders first, the rest where they fit

Not every feature deserves a change in old code. The first two steps change only what a reader sees: a deletion reason changes the error message and nothing else, and _ names values that are never read, so neither changes what the program does. Computed messages and pack indexing change templates that other code instantiates, so they come next, one template at a time. Walk through a useful order for a code base moving to C++26, step by step:

Timeline · 5 events in order Adopting the smaller C++26 features, in a useful order Walk through the steps in order. Each says what to change in the code base and why it comes at that point.
  1. 1. Reasons on delete

    A reason on every deleted overload a caller can hit start here

    Write = delete("…") with a reason wherever a caller might pick the deleted overload by mistake. Each reason saves a search through headers the next time someone gets the error, because both compilers print it after the function's name.

  2. 2. The placeholder _

    _ for guards and ignored bindings

    Call lock guards, scope guards and the parts of a structured binding you ignore _. That removes [[maybe_unused]] attributes and (void) casts from the code.

  3. 3. Computed messages

    static_assert messages that quote the argument

    In templates that other teams instantiate, build the message at compile time so that it can quote the argument that was wrong, such as the size of a buffer that was too small.

  4. 4. Pack indexing

    Ts...[N] wherever generic code needs the N-th element

    Replace recursive helpers that peel one type at a time with Ts...[N] or args...[N]. It reads better and builds faster than a recursive helper.

  5. 5. Where they fit

    Variadic friends and #embed, where their problem exists when needed

    Use friend Ts...; for a template that grants access to a list of types, and #embed for binary data such as test fixtures, certificates or shaders that would otherwise need a generator step.

The timeline has 5 events, from 1. Reasons on delete to 5. Where they fit.

Start where a change saves the next reader time, and leave variadic friends and #embed until code has the problem they solve.

Checkpoint

Check: Which feature to adopt first

  1. Question 1 of 2 In the lesson's order, which change does a code base moving to C++26 make first?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Reasons on every deleted overload a caller might hit by mistake

    The order runs from the changes that save the next reader time to the ones that solve a specific problem: deletion reasons, then _, then computed messages, then pack indexing, then variadic friends and #embed.

  2. Question 2 of 2 A project keeps test certificates in files, and a build script turns each one into a .cpp array. Which feature of this lesson removes that step?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: #embed, which turns a file's bytes into a list of numbers at compile time

    #embed puts a file's bytes into the source as a comma-separated list, without a generated source file; in features.cpp it turned status.txt into three bytes starting with 'o'.

Your own words in the compiler’s error

Two of these features exist for the person who gets a compile error. The file has two deliberate mistakes, chosen with -DMISTAKE:

diagnostics.cpp C++ · diagnostics.cpp
// Two mistakes whose error messages C++26 lets the author of the code write. Build with -DMISTAKE=1 or -DMISTAKE=2.
#include <cstddef>
#include <string_view>

void set_timeout_ms(long ms);
void set_timeout_ms(double) = delete("pass whole milliseconds as a long, not seconds as a double");

// A message built at compile time from a template argument: the text names the size that was too small.
template <std::size_t N> struct TooSmall {
  char text[64]{};
  std::size_t length = 0;
  constexpr TooSmall() {
    append("record buffer of ");
    append_number(N);
    append(" bytes, need at least 16");
  }
  constexpr void append(std::string_view s) {
    for (char c : s) text[length++] = c;
  }
  constexpr void append_number(std::size_t n) {
    char digits[20];
    int count = 0;
    do digits[count++] = static_cast<char>('0' + n % 10); while (n /= 10);
    while (count > 0) text[length++] = digits[--count];
  }
  constexpr std::size_t size() const { return length; }
  constexpr const char* data() const { return text; }
};

template <std::size_t N> struct RecordBuffer {
  static_assert(N >= 16, TooSmall<N>{});
  char bytes[N];
};

int main() {
#if MISTAKE == 1
  set_timeout_ms(0.25);
#elif MISTAKE == 2
  RecordBuffer<8> buffer;
  (void)buffer;
#endif
}
Two mistakes, both compilers, the error lines only Python · diagnostics_session.py
from session import run

# Each build fails on purpose. Only the error lines are shown; each compiler adds notes below them.
run("""
clang++ -std=c++26 -DMISTAKE=1 -c diagnostics.cpp -o /dev/null
g++-16 -std=c++26 -DMISTAKE=1 -c diagnostics.cpp -o /dev/null
clang++ -std=c++26 -DMISTAKE=2 -c diagnostics.cpp -o /dev/null
g++-16 -std=c++26 -DMISTAKE=2 -c diagnostics.cpp -o /dev/null
""", keep=r"error:")

Output

$ clang++ -std=c++26 -DMISTAKE=1 -c diagnostics.cpp -o /dev/null
diagnostics.cpp:37:3: error: call to deleted function 'set_timeout_ms': pass whole milliseconds as a long, not seconds as a double
[9 more lines not shown]
[exit status 1]
$ g++-16 -std=c++26 -DMISTAKE=1 -c diagnostics.cpp -o /dev/null
diagnostics.cpp:37:17: error: use of deleted function 'void set_timeout_ms(double)': pass whole milliseconds as a long, not seconds as a double
[9 more lines not shown]
[exit status 1]
$ clang++ -std=c++26 -DMISTAKE=2 -c diagnostics.cpp -o /dev/null
diagnostics.cpp:31:17: error: static assertion failed due to requirement '8UL >= 16': record buffer of 8 bytes, need at least 16
[6 more lines not shown]
[exit status 1]
$ g++-16 -std=c++26 -DMISTAKE=2 -c diagnostics.cpp -o /dev/null
diagnostics.cpp:31:19: error: static assertion failed: record buffer of 8 bytes, need at least 16
[7 more lines not shown]
[exit status 1]

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

Step through the first mistake, with and without the deleted overload:

Interactive · step by step set_timeout_ms(0.25): from a silent 0 ms to an error with a reason Step through one call with a double argument, first with only the long overload, then with a deleted double overload.
set_timeout_ms(0.25): from a silent 0 ms to an error with a reason, step 7 of 7. Showing set_timeout_ms(0.25), set_timeout_ms(long), set_timeout_ms(double), an error with the reason (both compilers quote it).converts 0.250.25 becomes 0exact matchchosen and deletedset_timeout_ms(0.25)meant a quarter secondset_timeout_ms(long)whole millisecondsset_timeout_ms(double)= delete("pass whole …")an error with the reasonboth compilers quote it

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. A quarter of a second. A caller writes set_timeout_ms(0.25), meaning a quarter of a second. The function takes whole milliseconds as a long.
  2. Only the long overload. With only set_timeout_ms(long) declared, the double converts to long without a word, and 0.25 becomes 0.
  3. A timeout of 0 ms. The call compiles and sets a timeout of 0 milliseconds: the bug the deleted overload exists to stop.
  4. Add a deleted overload. Declare set_timeout_ms(double) = delete("pass whole milliseconds as a long, not seconds as a double"). A deleted function still takes part in overload resolution.
  5. The exact match wins. For a double argument the deleted overload is an exact match, and the long one needs a conversion, so overload resolution picks the deleted one.
  6. An error with your reason. Calling a deleted function is an error. Clang said call to deleted function 'set_timeout_ms' and GCC use of deleted function, and both printed the reason after the name.
  7. What C++23 could say. Before C++26 the error could only say that the function was deleted; the reason was a comment the caller had to find next to the declaration.

The second mistake instantiates RecordBuffer<8>, and the message was built by TooSmall<8> during compilation: “record buffer of 8 bytes, need at least 16”, where C++23 could only say a fixed text. Clang also prints the condition with its values, 8UL >= 16; GCC prints the message alone.

Hex Viewer & File Inspector Open the file you #embed to see the bytes the compiler pastes in, one number per byte.

Key idea

A deletion reason and a computed static_assert message turn a compile error into documentation: the caller reads what to do, in your words, at the line that broke.

Checkpoint

Check: Error messages you write yourself

  1. Question 1 of 2 Why does declaring set_timeout_ms(double) = delete("…") stop set_timeout_ms(0.25), instead of letting 0.25 convert to long?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The deleted overload still takes part in overload resolution, and for a double it is an exact match, so it is chosen and the call is an error

    Without the deleted overload, set_timeout_ms(0.25) converts the double to long and sets a timeout of 0 ms. With it, the exact match is chosen, and both compilers print the reason after the function's name.

  2. Question 2 of 2 For RecordBuffer<8>, what did Clang print that GCC did not?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The condition with its values, 8UL >= 16

    The message was built by TooSmall<8> during compilation, where C++23 could only print a fixed text. Clang also prints the failed condition with its values; GCC prints the message alone.

Practise: the smaller language features

These questions cover the eight features, the order in which to adopt them, and the errors whose words you write.

Practice that adapts to you

Practise this lesson

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

  1. Question 1 of 11 What does last(1, 2.5, 'x') return?

    Read the code, then choose one answer.

    template <class... Args> auto last(Args... args) { return args...[sizeof...(args) - 1]; }
    Show the answer: Question 1 of 11

    Answer: 'x', a char

    The packs are Args = {int, double, char} and args = {1, 2.5, 'x'}, as the diagram shows, and index 2 picks 'x', of type char.

  2. Question 2 of 11 In C++26 mode with -pedantic, which compiler of this track warns that #embed is an extension?

    Choose one answer.

    Show the answer: Question 2 of 11

    Answer: Apple clang 21

    Clang implements the directive, which C23 has too, but does not list it as done for C++26, so -pedantic calls it a Clang extension. GCC 16 accepts it as standard C++26.

  3. Question 3 of 11 What does = delete("pass whole milliseconds as a long") add to a plain = delete?

    Choose one answer.

    Show the answer: Question 3 of 11

    Answer: The compiler prints the reason in its error when someone calls the function

    A deleted function still takes part in overload resolution; choosing it is an error. With C++26 that error carries the reason, as both compilers showed.

  4. Question 4 of 11 In if (auto [value, ok] = parse_digit('7')), what decides whether the if body runs?

    Choose one answer.

    Show the answer: Question 4 of 11

    Answer: The returned Parsed object, through its explicit operator bool

    The if ran because parse_digit('7') returned an object whose explicit operator bool was true, and the body could use value and ok by name.

  5. Question 5 of 11 Vault<Readers...> declares friend Readers...;. Which types can read secret in a Vault<Auditor, Logger>?

    Choose one answer.

    Show the answer: Question 5 of 11

    Answer: Auditor and Logger, and no other type

    friend Readers...; declares a whole pack of friends in one line, so a template can open its private members to the types it is given, as Vault<Auditor> did for Auditor.

  6. Question 6 of 11 Why do deletion reasons come first in the lesson's adoption order?

    Choose one answer.

    Show the answer: Question 6 of 11

    Answer: Each one saves a search through headers the next time someone gets the error

    The order starts with what saves the next reader time: a reason on every deleted overload a caller might hit by mistake.

  7. Question 7 of 11 Using _ for lock guards, scope guards and ignored bindings removes which workarounds?

    Choose every answer that is right.

    Show the answer: Question 7 of 11

    Answer:

    • [[maybe_unused]] attributes
    • (void) casts

    The placeholder _ is for values you must name but never read, so the attributes and casts that silenced warnings about them go away.

  8. Question 8 of 11 What does the lesson give as the reason to replace recursive helpers with Ts...[N]?

    Choose one answer.

    Show the answer: Question 8 of 11

    Answer: It reads better and builds faster than a recursive helper

    Pack indexing names the N-th element directly, where C++23 needed a recursive template or std::tuple_element_t, so generic code reads better and builds faster.

  9. Question 9 of 11 Before C++26, what could a compiler say when code called a deleted function?

    Choose one answer.

    Show the answer: Question 9 of 11

    Answer: Only that the function was deleted

    C++26 puts the reason into the error message itself; before it, the error could only say that the function was deleted.

  10. Question 10 of 11 Where did the text "record buffer of 8 bytes, need at least 16" come from?

    Choose one answer.

    Show the answer: Question 10 of 11

    Answer: TooSmall<8> built it during compilation, from the template argument

    RecordBuffer<8> instantiated static_assert(N >= 16, TooSmall<N>{}), and TooSmall<8> built the text at compile time, where C++23 could only print a fixed text.

  11. Question 11 of 11 A template argument breaks a condition, such as a buffer size below 16. Which tool explains the mistake best, and why?

    Choose one answer.

    Show the answer: Question 11 of 11

    Answer: A computed static_assert message, because it can quote the argument that was wrong

    = delete("…") is for calls that should not compile, such as a double for a timeout; a computed static_assert message is for a template argument that breaks a condition. Both put your words in the error.

Each feature in depth: rules, traps and costs, measured Pro: opens with a Pro, Premium or Ultimate pass

Small features have small rules, and each rule shows up as a compile error the first time it is broken. This part reproduces the traps of pack indexing and of _ on both compilers, and measures what pack indexing saves at compile time against the two older ways.

In this part:

  • Two pack-indexing traps, reproduced on both compilers
  • What pack indexing saves at compile time, measured
  • The rules of the placeholder, and its one error
  • Variadic friends in practice: a passkey with a list of holders
  • Messages as documentation: writing them well

8 runnable examples (C++, Python) · about 10 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Moving a C++23 code base to C++26: what breaks, reproduced Pro: opens with a Pro, Premium or Ultimate pass

Switching a project to -std=c++26 adds features, and it also removes things that were deprecated for years. This part compiles five such constructs in both modes on both compilers, shows how to guard new features with their macros, and ends with a refactoring exercise checked by sample tests.

In this part:

  • Five constructs C++26 rejects or warns about, compiled both ways
  • Guarding new features with their macros
  • Your turn: refactor a C++23 report program

6 runnable examples (C++, Python) · about 6 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher to mid · Warm-up

    What does pack indexing, Ts...[N], do in C++26, and what did code use before it?

    It names the N-th element of a pack directly, counting from 0. For a type pack, Ts...[0] is the first type; for a function parameter pack, args...[1] is the second argument, with the declared type of that parameter. N must be a constant expression, and an index past the end is a compile error that names the index and the size of the pack. Before C++26, code got the N-th type with a recursive template that peeled one type at a time, or with std::tuple_element_t<N, std::tuple<Ts...>>, and the N-th value with std::get<N>(std::forward_as_tuple(args...)). Pack indexing is shorter, reads like an array index, and compiles faster than the recursive version, because the compiler picks the element without instantiating a chain of templates. Apple clang 21 and GCC 16 both support it in C++26 mode.

  2. Mid to senior

    A caller passes 0.5 to set_timeout_ms(long), meaning half a second, and gets a timeout of 0 ms. How do you make such calls fail at compile time with a clear message in C++26, and how does that compare with what C++23 offered?

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

  3. Senior to staff

    Your team moves a large C++23 code base to -std=c++26 with Apple clang 21 and GCC 16. What breaks first, how do you find it across the whole code base, and how do you keep both modes building during the transition?

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    Compare three ways to get the N-th type of a template parameter pack: a recursive template, std::tuple_element_t and C++26 pack indexing. Which would you use in a library today, and how would you measure the difference?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Pack indexing, _, = delete("reason"), computed static_assert messages and variadic friends work on both compilers in C++26 mode; #embed works on both, and Apple clang 21 calls it an extension.
  • Adopt deletion reasons and the placeholder _ first, then computed messages and pack indexing, and variadic friends and #embed where their problem exists.
  • Write reasons and messages for the caller: both compilers print a deletion reason after the function’s name, and a computed static_assert message can quote the argument that was wrong.

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.