C++23 & C++26

C++23 & C++26 Cookbook

Self-contained recipes for the C++20-fluent developer. Each entry emphasizes the delta from C++20: the problem it solves, a minimal compilable example, gotchas, and current compiler support. Built for 60-second reads.

Showing 43 of 43 recipes

C++23

C++23LanguageP0847

Deducing this (explicit object parameter)

Name the object parameter to deduce its value category and constness.

Problem

C++20 forced you to write 2–4 ref-qualified overloads (const&, &, &&, const&&) to forward `*this` correctly, and CRTP was the only way to get the derived type in a base method.

C++20 — before
struct S {
  const T& get() const &  { return v; }
  T&       get()       &  { return v; }
  T&&      get()       && { return std::move(v); }
};
C++23 — after
#include <utility>
struct S {
  T v;
  // One template covers every cv/ref combination:
  template <class Self>
  auto&& get(this Self&& self) {
    return std::forward<Self>(self).v;
  }
};

// Deducing-this also kills CRTP boilerplate:
struct Base {
  template <class Self>
  void interface(this Self&& self) { self.impl(); }
};
struct Derived : Base { void impl() { /* ... */ } };

// And enables recursive lambdas without std::function:
auto fac = [](this auto self, int n) -> int {
  return n <= 1 ? 1 : n * self(n - 1);
};

Notes

  • The explicit object parameter replaces the implicit `this`; inside the function `this` is NOT available — use `self`.
  • Cannot be combined with static, virtual, or cv/ref qualifiers on the same function.
  • Great for de-duplicating getters and for perfect-forwarding call wrappers; enables by-value `this` (e.g. small handles).

Compiler support

GCCGCC 14
ClangClang 18
MSVCMSVC 19.32 / VS 17.2
Try on Compiler Explorer
C++23LanguageP1169 / P2589

static operator() and static operator[]

Stateless function objects with no implicit object parameter.

Problem

C++20 function objects always took a (often unused) `this`, blocking some optimizations and adding an argument for stateless comparators/projections.

C++23
#include <algorithm>
#include <vector>

struct Less {
  static bool operator()(int a, int b) { return a < b; } // no 'this'
};

struct Identity {
  static constexpr auto operator[](int i) { return i; }   // static subscript
};

int main() {
  std::vector v{3, 1, 2};
  std::ranges::sort(v, Less{}); // Less{} carries no state
}

Notes

  • Only meaningful for stateless callables; the compiler can skip passing the object.
  • Pairs naturally with C++23's multidimensional `operator[]`.
  • Lambdas can be made static too: `[](int x) static { return x; }`.

Compiler support

GCCGCC 13
ClangClang 16
MSVCMSVC 19.34 / VS 17.4
Try on Compiler Explorer
C++23LanguageP1938

if consteval

Branch on whether you are in a constant-evaluation context.

Problem

`std::is_constant_evaluated()` inside `if constexpr` is a trap (always true), and it cannot call `consteval` functions on the compile-time branch.

C++20 — before
constexpr int f(int x) {
  if (std::is_constant_evaluated()) { /* cannot call consteval fns here */ }
  return x;
}
C++23 — after
consteval int ct(int x) { return x * x; }

constexpr int f(int x) {
  if consteval {
    return ct(x);     // OK: consteval call allowed only here
  } else {
    return x * x;     // runtime path
  }
}
static_assert(f(3) == 9);

Notes

  • `if consteval` is a real keyword pair — no condition, no `()`.
  • Only inside this branch may you call `consteval` (immediate) functions during constant evaluation.
  • Prefer it over `if (std::is_constant_evaluated())` whenever you need to call immediate functions.

Compiler support

GCCGCC 12
ClangClang 14
MSVCMSVC 19.32 / VS 17.2
Try on Compiler Explorer
C++23LanguageP1774 / P0627

[[assume(expr)]] and std::unreachable()

Hand the optimizer invariants it cannot prove.

Problem

C++20 had only compiler-specific intrinsics (`__builtin_assume`, `__assume`) to express undefined-on-violation assumptions.

C++23
#include <utility>   // std::unreachable
#include <cmath>

int divide(int x) {
  [[assume(x > 0)]];          // UB if x <= 0; lets compiler drop checks
  return 1'000'000 / x;
}

int classify(int e) {
  switch (e) {
    case 0: return 10;
    case 1: return 20;
  }
  std::unreachable();         // tells optimizer no other case occurs
}

Notes

  • `[[assume]]` does NOT evaluate its expression — never put side effects in it.
  • Violating an assume (or reaching `std::unreachable()`) is undefined behavior, not a diagnostic. Use sparingly and only for proven invariants.
  • Contrast with C++26 Contracts, which CHECK conditions instead of assuming them.

Compiler support

GCCGCC 13
ClangClang 19
MSVCMSVC 19.33 / VS 17.3
Try on Compiler Explorer
C++23LanguageP2128

Multidimensional operator[](a, b, ...)

Comma-separated subscripts as a first-class operator.

Problem

In C++20 `m[i, j]` invoked the comma operator (and was deprecated); multi-index access needed `m(i, j)` or `m[i][j]`.

C++20 — before
// C++20: operator() workaround
double& operator()(std::size_t r, std::size_t c);
auto x = m(1, 2);
C++23 — after
#include <vector>
struct Matrix {
  std::size_t cols;
  std::vector<double> data;
  double& operator[](std::size_t r, std::size_t c) {
    return data[r * cols + c];
  }
};

int main() {
  Matrix m{3, std::vector<double>(9)};
  m[1, 2] = 4.0;   // true 2D subscript
}

Notes

  • This is what makes `std::mdspan`'s `ms[i, j, k]` ergonomic.
  • The old comma-in-subscript meaning was removed; `arr[i, j]` on a built-in array is now ill-formed (was deprecated in C++20).

Compiler support

GCCGCC 12
ClangClang 15
MSVCMSVC 19.36 / VS 17.6
Try on Compiler Explorer
C++23LanguageP0330

size_t literals: uz / z

Write std::size_t / signed-size literals directly.

Problem

Comparing a loop index against `v.size()` forced casts or `-Wsign-compare` warnings; there was no literal suffix for size_t.

C++20 — before
for (std::size_t i = 0; i < v.size(); ++i) {}  // verbose
// or: for (auto i = 0u; ...) // wrong type on LP64
C++23 — after
#include <vector>
int main() {
  std::vector<int> v(10);
  for (auto i = 0uz; i < v.size(); ++i) {  // i is std::size_t
    v[i] = static_cast<int>(i);
  }
  auto signed_size = 5z;   // 'z' -> signed counterpart (std::ptrdiff_t-ish)
}

Notes

  • `uz`/`uZ` → `std::size_t`; `z`/`Z` → the signed type of the same width.
  • Mostly removes sign-compare warnings in index loops; still prefer range-for / `enumerate` where possible.

Compiler support

GCCGCC 11
ClangClang 13
MSVCMSVC 19.34 / VS 17.4
Try on Compiler Explorer
C++23LibraryP2465

import std;

Pull the entire standard library in as a single named module.

Problem

C++20 modules existed, but the standard library was still consumed via textual #include — so you paid preprocessor + parsing costs for <vector>, <string>, etc. on every TU.

C++20 — before
#include <vector>
#include <string>
#include <print>
// dozens of headers, re-parsed per TU
C++23 — after
// main.cpp — build as a module TU
import std;            // everything in namespace std
// import std.compat; // also injects the global :: C names (::printf, ::size_t)

int main() {
  std::vector<std::string> v{"import", "std", "is", "fast"};
  std::println("{}", v);
}
// Output: ["import", "std", "is", "fast"]

Notes

  • Two modules: `std` (namespace std only) and `std.compat` (also the global-namespace C library names).
  • You cannot mix `import std;` with macros from headers it replaces — macros are not exported by modules.
  • Build-system support is the real gate: you must build the std module BMI first. CMake 3.30+ has `import std` support behind a feature flag.
  • Massive compile-time win, but only once the std module is prebuilt; the first build still compiles libstdc++/libc++ sources.

Compiler support

GCCGCC 15 (libstdc++, experimental)
ClangClang 17+ (libc++, with -fmodules)
MSVCMSVC 19.36 / VS 17.6
Try on Compiler Explorer
C++23LibraryP0323 / P2505

std::expected<T, E>

Vocabulary type for fallible returns, with monadic chaining.

Problem

C++20 had `std::optional` (no error payload) and exceptions (heavy, not always allowed). There was no standard way to return value-or-error by value.

C++20 — before
// C++20: optional loses the error; or throw; or out-params
std::optional<int> parse(std::string_view);
C++23 — after
#include <expected>
#include <string>
#include <charconv>

std::expected<int, std::string> parse(std::string_view s) {
  int out{};
  auto [p, ec] = std::from_chars(s.data(), s.data() + s.size(), out);
  if (ec != std::errc{}) return std::unexpected("bad int");
  return out;
}

int main() {
  auto r = parse("21")
    .transform([](int x) { return x * 2; })   // map value
    .or_else([](std::string e)                // recover from error
       -> std::expected<int, std::string> { return 0; });
  // r.value() == 42
}

Notes

  • Monadic ops: `and_then`, `transform`, `or_else`, `transform_error` — same family as `optional`'s.
  • `std::unexpected<E>` wraps the error; `operator bool()` / `has_value()` test success.
  • `.value()` throws `std::bad_expected_access<E>` on error — use `.value_or()` or monadic ops to stay exception-free.
  • `expected<void, E>` is valid for operations that return only success/failure.

Compiler support

GCCGCC 12
ClangClang 16 (libc++)
MSVCMSVC 19.33 / VS 17.3
Try on Compiler Explorer
C++23LibraryP2093

std::print / std::println

Formatted, Unicode-correct output without iostreams overhead.

Problem

C++20 gave us `std::format` but you still had to feed it to `std::cout`, paying for iostreams and losing a clean printf-like call.

C++20 — before
#include <format>
#include <iostream>
std::cout << std::format("{} + {} = {}\n", 1, 2, 3);
C++23 — after
#include <print>
#include <vector>

int main() {
  std::println("{} + {} = {}", 1, 2, 3);   // newline included
  std::print("no newline");

  std::vector v{1, 2, 3};
  std::println("{}", v);                     // ranges format out of the box
  std::println("{:#06x}", 255);              // 0x00ff
}
// Output:
// 1 + 2 = 3
// no newline[...]
// [1, 2, 3]
// 0x00ff

Notes

  • Writes directly to a FILE* (stdout by default) and handles UTF-8 transcoding to the console on Windows.
  • `std::println(stderr, ...)` for error streams; pass any `std::FILE*`.
  • Compile-time checked format strings, same as `std::format`.

Compiler support

GCCGCC 14
ClangClang 18 (libc++)
MSVCMSVC 19.37 / VS 17.7
Try on Compiler Explorer
C++23LibraryP2286

Formatting ranges & containers

std::format now knows how to print any range, map, tuple, or pair.

Problem

In C++20 `std::format("{}", vec)` was ill-formed — you had to hand-roll a loop or a custom formatter for every container.

C++23
#include <format>
#include <print>
#include <map>
#include <vector>
#include <set>

int main() {
  std::println("{}", std::vector{1, 2, 3});         // [1, 2, 3]
  std::println("{}", std::map<int,int>{{1,10},{2,20}}); // {1: 10, 2: 20}
  std::println("{:n}", std::set{1, 2, 3});           // 1, 2, 3  (no brackets)
  std::println("{::#x}", std::vector{10, 11});       // [0xa, 0xb] nested spec
}

Notes

  • Range spec syntax: outer spec for the range, then `::` introduces the per-element spec.
  • `{:n}` strips the enclosing brackets/braces; `{:m}` formats associative ranges as a map-of-pairs.
  • Strings inside ranges are quoted/escaped by default (debug formatting via `?`).

Compiler support

GCCGCC 14
ClangClang 17 (libc++ partial)
MSVCMSVC 19.36 / VS 17.6
Try on Compiler Explorer
C++23LibraryP2502

std::generator<T>

The standard coroutine generator — finally a return type for co_yield.

Problem

C++20 shipped coroutine machinery but NO concrete generator type, so everyone wrote their own promise_type or pulled in a library.

C++23
#include <generator>
#include <print>

std::generator<int> fib() {
  int a = 0, b = 1;
  while (true) {
    co_yield a;
    a = std::exchange(b, a + b);
  }
}

int main() {
  for (int x : fib() | std::views::take(8))
    std::print("{} ", x);
}
// Output: 0 1 1 2 3 5 8 13

Notes

  • Models `input_range`, so it composes with Ranges views directly.
  • Supports recursive yielding via `co_yield std::ranges::elements_of(inner())` for tree/graph traversal without manual stacking.
  • Move-only and single-pass; don't expect to iterate twice.

Compiler support

GCCGCC 14
ClangClang 18 (libc++ 18)
MSVCMSVC 19.39 / VS 17.9
Try on Compiler Explorer
C++23LibraryP0009

std::mdspan

Non-owning multidimensional view over contiguous storage.

Problem

C++20 had `std::span` (1D only). Multi-dim numeric code needed manual index math or third-party libs (Kokkos, Eigen views).

C++23
#include <mdspan>
#include <vector>
#include <print>

int main() {
  std::vector<double> buf(2 * 3, 0.0);
  std::mdspan m(buf.data(), 2, 3);     // 2x3 view, dynamic extents

  for (std::size_t i = 0; i < m.extent(0); ++i)
    for (std::size_t j = 0; j < m.extent(1); ++j)
      m[i, j] = i * 10 + j;            // multidim subscript

  std::println("{}", m[1, 2]);          // 12
}

Notes

  • Owns nothing — it is a view (pointer + extents + layout + accessor).
  • Layout policies: `layout_right` (row-major, default), `layout_left` (column-major, BLAS/Fortran), `layout_stride`.
  • C++26 adds `submdspan` for slicing; `mdarray` (owning) is a separate, later proposal.

Compiler support

GCCGCC 14
ClangClang 18 (libc++)
MSVCMSVC 19.39 / VS 17.9
Try on Compiler Explorer
C++23LibraryP0429 / P1222

std::flat_map / std::flat_set

Sorted associative containers backed by contiguous storage.

Problem

`std::map`/`set` are node-based: poor cache locality and an allocation per element. C++20 had no standard cache-friendly sorted map.

C++23
#include <flat_map>
#include <print>

int main() {
  std::flat_map<int, std::string> fm{{3,"c"}, {1,"a"}, {2,"b"}};
  fm.try_emplace(4, "d");
  for (auto&& [k, v] : fm) std::print("{}:{} ", k, v);
  // 1:a 2:b 3:c 4:d  (kept sorted, keys & values in parallel vectors)
}

Notes

  • Adaptors, not new containers: store keys and values in two sorted sequences (default `std::vector`).
  • Fast lookup (binary search) and iteration; SLOW single inserts/erases (O(n) shifts). Bulk-build, then query.
  • `flat_multimap` / `flat_multiset` variants exist. Iterators/references are invalidated by any modification.

Compiler support

GCCGCC 15 (partial)
Clanglibc++ not yet shipped
MSVCMSVC 19.40 / VS 17.10
Try on Compiler Explorer
C++23LibraryP0288

std::move_only_function

A std::function that can hold move-only callables.

Problem

`std::function` requires the target to be copyable, so you couldn't store a lambda capturing a `unique_ptr` or a `std::promise`.

C++23
#include <functional>
#include <memory>

int main() {
  auto p = std::make_unique<int>(42);
  std::move_only_function<int()> f =
    [p = std::move(p)] { return *p; };   // capture move-only state

  int r = f();  // 42
}

Notes

  • Move-only; calling it after move-from is UB. Supports cv/ref/noexcept qualifiers in the signature (e.g. `int() const`).
  • Use it for task queues, deferred work, and one-shot callbacks where copyability was the only blocker.
  • C++26 adds `std::function_ref` (non-owning) and `std::copyable_function` to round out the family.

Compiler support

GCCGCC 12
ClangClang 16 (libc++)
MSVCMSVC 19.32 / VS 17.2
Try on Compiler Explorer
C++23LibraryP1132

std::out_ptr / std::inout_ptr

Adapt smart pointers to C APIs that take T**.

Problem

Calling C APIs like `create(&raw)` then wrapping in a `unique_ptr` was a manual, leak-prone dance in C++20.

C++23
#include <memory>
#include <cstdio>

int main() {
  std::unique_ptr<std::FILE, decltype(&std::fclose)> fp{nullptr, &std::fclose};

  // Imagine: int api_open(FILE** out, const char* path);
  // api_open(std::out_ptr(fp), "data.txt");
  // out_ptr resets fp and feeds &raw to the C function, then adopts it.
}

Notes

  • `std::out_ptr` resets the smart pointer first (for pure out-params); `std::inout_ptr` passes the existing value in and back out.
  • Works with `unique_ptr`, `shared_ptr`, and custom smart pointers; you can pass extra deleter/args.
  • Eliminates the raw-temporary + manual `.reset()` pattern around legacy C handle APIs.

Compiler support

GCCGCC 12
ClangClang 19 (libc++)
MSVCMSVC 19.32 / VS 17.2
Try on Compiler Explorer
C++23LibraryP1682

std::to_underlying

Convert a scoped enum to its underlying integer, intent-revealing.

Problem

`static_cast<std::underlying_type_t<E>>(e)` is verbose and easy to get wrong (wrong target type) for `enum class`.

C++20 — before
enum class Color : unsigned { red, green };
auto n = static_cast<std::underlying_type_t<Color>>(Color::green);
C++23 — after
#include <utility>
enum class Color : unsigned { red, green, blue };

int main() {
  auto n = std::to_underlying(Color::green);  // unsigned, == 1
}

Notes

  • Pure convenience wrapper; constexpr and noexcept.
  • Pairs well with reflection-free enum→int serialization paths.

Compiler support

GCCGCC 11
ClangClang 13
MSVCMSVC 19.31 / VS 17.1
Try on Compiler Explorer
C++23LibraryP1272

std::byteswap

Reverse byte order portably and at compile time.

Problem

Endian swaps relied on `__builtin_bswap*` / `_byteswap_*` intrinsics — non-portable and not constexpr-uniform.

C++23
#include <bit>
#include <cstdint>

int main() {
  std::uint32_t host = 0x12345678;
  std::uint32_t swapped = std::byteswap(host);  // 0x78563412
  static_assert(std::byteswap<std::uint16_t>(0x00FF) == 0xFF00);
}

Notes

  • Works on any integral type; constexpr.
  • Combine with `std::endian` (C++20) to write portable serialization.
  • Does not byteswap floats directly — bit_cast to an integer first.

Compiler support

GCCGCC 12
ClangClang 14
MSVCMSVC 19.33 / VS 17.3
Try on Compiler Explorer
C++23LibraryP0881

std::stacktrace

Capture and print a call stack from portable C++.

Problem

Getting a backtrace meant platform-specific code (`backtrace()`, `CaptureStackBackTrace`, libunwind).

C++23
#include <stacktrace>
#include <print>

void deep() {
  std::println("{}", std::stacktrace::current());
}
int main() { deep(); }
// Output: frame list with addresses, functions, file:line (if symbols present)

Notes

  • `std::stacktrace::current()` snapshots; entries expose `description()`, `source_file()`, `source_line()`.
  • Requires the backtrace support library at link time (e.g. `-lstdc++exp` / `-lstdc++_libbacktrace` on GCC).
  • Quality depends on debug info; release builds may show only addresses.

Compiler support

GCCGCC 12 (link -lstdc++exp)
Clanglibc++ not yet shipped
MSVCMSVC 19.34 / VS 17.4
Try on Compiler Explorer
C++23LibraryP0448

std::spanstream

iostream over a fixed user-provided buffer — no allocation.

Problem

`std::stringstream` always owns and allocates a `std::string`; `strstream` (the non-owning option) was deprecated/removed.

C++23
#include <spanstream>
#include <span>
#include <print>

int main() {
  char buf[64];
  std::ospanstream os{std::span<char>(buf)};
  os << "x=" << 42;
  std::println("{}", os.span().size());  // bytes written, no heap use
}

Notes

  • `ispanstream` / `ospanstream` / `spanstream` parse/format into caller-owned memory.
  • Ideal for embedded / hot paths where stringstream allocation is unacceptable.
  • You manage buffer capacity; overflow on output just stops writing (stream fails).

Compiler support

GCCGCC 12
Clanglibc++ not yet shipped
MSVCMSVC 19.31 / VS 17.1
Try on Compiler Explorer
C++23LibraryP2590

std::start_lifetime_as

Legitimize objects in a byte buffer without UB.

Problem

Reinterpreting received bytes as a struct was technically UB in C++20 (no object lifetime started); `bit_cast` copies and needs trivially-copyable.

C++23
#include <memory>   // std::start_lifetime_as
#include <cstddef>
#include <cstring>

struct Header { std::uint32_t magic, len; };

const Header* parse(std::byte* data, std::size_t) {
  // Begins a Header lifetime over the existing bytes, no copy:
  return std::start_lifetime_as<Header>(data);
}

Notes

  • Only valid for implicit-lifetime types; the bytes must already hold a valid representation.
  • `start_lifetime_as_array` handles buffers of N objects.
  • Use instead of `reinterpret_cast` when adopting deserialized / mmap'd / DMA buffers.

Compiler support

GCCGCC 14 (partial)
Clanglibc++ not yet shipped
MSVCMSVC 19.40 / VS 17.10
Try on Compiler Explorer
C++23LibraryP2273 / P0533

Expanded constexpr: unique_ptr, <cmath>, more

More of the standard library usable during constant evaluation.

Problem

C++20 made allocation constexpr but `std::unique_ptr` and most `<cmath>` functions were still runtime-only.

C++23
#include <memory>
#include <cmath>

constexpr int build() {
  auto p = std::make_unique<int>(40);   // constexpr unique_ptr (C++23)
  return *p + 2;
}
static_assert(build() == 42);

// Many <cmath> functions are constexpr in C++23/26:
static_assert(std::abs(-3) == 3);

Notes

  • `constexpr` `std::unique_ptr` enables compile-time owning data structures (must be freed before the evaluation ends).
  • `<cmath>` constexpr-ness expanded across C++23 and continues in C++26 (P0533).
  • Heap allocated at compile time must not escape to runtime — it cannot leak into the final object.

Compiler support

GCCGCC 13
ClangClang 16
MSVCMSVC 19.36 / VS 17.6
Try on Compiler Explorer
C++23RangesP2321

views::zip / zip_transform

Iterate multiple ranges in lockstep as tuples.

Problem

C++20 ranges had no zip; parallel iteration meant index loops or Boost.

C++20 — before
for (std::size_t i = 0; i < a.size(); ++i)
  use(a[i], b[i]);
C++23 — after
#include <ranges>
#include <vector>
#include <print>

int main() {
  std::vector a{1, 2, 3};
  std::vector b{'a', 'b', 'c'};
  for (auto [x, y] : std::views::zip(a, b))
    std::print("{}{} ", x, y);                 // 1a 2b 3c

  for (int s : std::views::zip_transform(std::plus{}, a, a))
    std::print("{} ", s);                       // 2 4 6
}

Notes

  • Length is the minimum of the inputs; elements are tuples of references — you can assign through them.
  • `zip_transform(f, rs...)` applies `f` element-wise without materializing tuples.
  • Related: `views::keys` / `views::values` / `views::elements<N>` to project tuple-like elements.

Compiler support

GCCGCC 13
ClangClang 17 (libc++ partial)
MSVCMSVC 19.36 / VS 17.6
Try on Compiler Explorer
C++23RangesP2164

views::enumerate

Index + element pairs, the Pythonic way.

Problem

C++20 forced a manual counter alongside range-for to get an index.

C++20 — before
std::size_t i = 0;
for (auto& e : v) { use(i, e); ++i; }
C++23 — after
#include <ranges>
#include <vector>
#include <print>

int main() {
  std::vector v{"red", "green", "blue"};
  for (auto [i, name] : std::views::enumerate(v))
    std::println("{}: {}", i, name);
}
// 0: red
// 1: green
// 2: blue

Notes

  • Yields `tuple<difference_type, range_reference_t>` — the index is signed.
  • Element is a reference; you can mutate `name` if `v` is non-const.

Compiler support

GCCGCC 13.1
ClangClang 19 (libc++)
MSVCMSVC 19.38 / VS 17.8
Try on Compiler Explorer
C++23RangesP2321

views::adjacent / adjacent_transform

Sliding windows of fixed compile-time width N.

Problem

Computing deltas or n-grams over a sequence needed manual offset bookkeeping in C++20.

C++23
#include <ranges>
#include <vector>
#include <print>

int main() {
  std::vector v{1, 3, 6, 10};
  // pairwise == adjacent<2>
  for (auto [a, b] : v | std::views::pairwise)
    std::print("{} ", b - a);                     // 2 3 4

  // adjacent_transform applies a function to each window
  for (int d : std::views::adjacent_transform<2>(v, std::minus{}))
    std::print("{} ", d);                         // -2 -3 -4
}

Notes

  • `adjacent<N>` gives N-tuples; `pairwise` is the alias for N=2.
  • Window width is a template (compile-time) parameter — contrast with `slide` (runtime width).
  • Empty if the range has fewer than N elements.

Compiler support

GCCGCC 13
ClangClang 19 (libc++)
MSVCMSVC 19.36 / VS 17.6
Try on Compiler Explorer
C++23RangesP2442 / P2443 / P2440

views::chunk / slide / stride / chunk_by

Batch, window, skip, and group adjacent elements.

Problem

C++20 had none of these common batching/windowing adaptors; you hand-rolled loops.

C++23
#include <ranges>
#include <vector>
#include <print>

int main() {
  std::vector v{1, 2, 3, 4, 5, 6};
  for (auto c : v | std::views::chunk(2))   std::println("{}", c); // [1,2][3,4][5,6]
  for (auto w : v | std::views::slide(3))   std::println("{}", w); // [1,2,3][2,3,4]...
  for (int  s : v | std::views::stride(2))  std::print("{} ", s);  // 1 3 5

  std::vector u{1, 1, 2, 2, 2, 3};
  for (auto g : u | std::views::chunk_by(std::equal_to{}))
    std::println("{}", g);                                          // [1,1][2,2,2][3]
}

Notes

  • `chunk(n)`: non-overlapping batches (last may be short). `slide(n)`: overlapping windows of exactly n.
  • `stride(n)`: every n-th element. `chunk_by(pred)`: split where `pred(prev, cur)` is false.
  • These are lazy views; combine freely with `transform`, `filter`, etc.

Compiler support

GCCGCC 13
ClangClang 19 (libc++ partial)
MSVCMSVC 19.37 / VS 17.7
Try on Compiler Explorer
C++23RangesP2441

views::join_with

Flatten a range of ranges, inserting a delimiter between them.

Problem

C++20 had `views::join` but no way to interleave a separator (the classic 'string join').

C++23
#include <ranges>
#include <vector>
#include <string>
#include <print>

int main() {
  std::vector<std::string> parts{"a", "b", "c"};
  auto joined = parts | std::views::join_with(',');
  std::string s(std::from_range, joined);   // "a,b,c"
  std::println("{}", s);
}

Notes

  • Delimiter can be a single value or a whole range (e.g. ", ").
  • Pairs with `ranges::to<std::string>()` to build the final string.

Compiler support

GCCGCC 13
ClangClang 19 (libc++)
MSVCMSVC 19.36 / VS 17.6
Try on Compiler Explorer
C++23RangesP2474

views::repeat

A lazy infinite (or bounded) range of one repeated value.

Problem

C++20 had no canonical 'repeat this value' generator; you abused `iota` + `transform` or wrote a custom view.

C++23
#include <ranges>
#include <print>

int main() {
  for (int x : std::views::repeat(7, 3))     // bounded: 7 7 7
    std::print("{} ", x);

  auto ones = std::views::repeat(1);          // infinite
  for (int x : ones | std::views::take(4))
    std::print("{} ", x);                     // 1 1 1 1
}

Notes

  • Single-argument form is infinite; two-argument form repeats n times.
  • A `repeat_view` is a `random_access_range`, so `take`, `drop`, indexing all work.

Compiler support

GCCGCC 13
ClangClang 17 (libc++)
MSVCMSVC 19.35 / VS 17.5
Try on Compiler Explorer
C++23RangesP2374

views::cartesian_product

Every combination across N ranges as tuples.

Problem

Nested loops over multiple ranges (grid sweeps, parameter spaces) had no range-based equivalent in C++20.

C++23
#include <ranges>
#include <print>

int main() {
  for (auto [x, y] : std::views::cartesian_product(
                       std::views::iota(0, 2),
                       std::views::iota(0, 3)))
    std::print("({},{}) ", x, y);
  // (0,0) (0,1) (0,2) (1,0) (1,1) (1,2)
}

Notes

  • Last range varies fastest (row-major order).
  • Size is the product of input sizes — easy to make enormous; combine with `take`.

Compiler support

GCCGCC 13
ClangClang 19 (libc++)
MSVCMSVC 19.36 / VS 17.6
Try on Compiler Explorer
C++23RangesP2278 / P2446

views::as_const / as_rvalue

Adjust element value category without copying.

Problem

C++20 lacked a clean way to view a mutable range as const, or to move elements out through a range pipeline.

C++23
#include <ranges>
#include <vector>
#include <string>

int main() {
  std::vector<std::string> src{"a", "b"};
  std::vector<std::string> dst;

  // Move each element out of src during iteration:
  for (auto&& s : src | std::views::as_rvalue)
    dst.push_back(std::move(s));   // s is an xvalue

  // Expose a read-only view (elements become const refs):
  auto cv = src | std::views::as_const;
}

Notes

  • `as_rvalue` turns each element into an rvalue reference — ideal for moving through `ranges::to` or algorithms.
  • `as_const` is the range analog of `std::as_const`; prevents accidental mutation through a view.

Compiler support

GCCGCC 13
ClangClang 19 (libc++)
MSVCMSVC 19.37 / VS 17.7
Try on Compiler Explorer
C++23RangesP1206

ranges::to

Materialize any range pipeline into a concrete container.

Problem

C++20 had no standard way to collect a view into a vector/map — you wrote a manual loop or `{begin, end}` ctor (which fails for views).

C++20 — before
auto v = some_view();
std::vector<int> out(v.begin(), v.end()); // breaks for many views
C++23 — after
#include <ranges>
#include <vector>
#include <map>

int main() {
  auto squares = std::views::iota(1, 5)
    | std::views::transform([](int x){ return x * x; })
    | std::ranges::to<std::vector>();          // {1,4,9,16}

  // Deduces value type; also builds associative containers:
  auto m = std::views::zip(std::views::iota(0,3), squares)
    | std::ranges::to<std::map<int,int>>();
}

Notes

  • Template-argument form `to<std::vector>()` deduces the element type; or specify it fully.
  • Recursively converts nested ranges; passes extra args to the container constructor (allocators, comparators).
  • Containers also gained `from_range_t` constructors (e.g. `std::vector(std::from_range, view)`).

Compiler support

GCCGCC 14
ClangClang 17 (libc++)
MSVCMSVC 19.34 / VS 17.4
Try on Compiler Explorer
C++23RangesP2322

ranges::fold_left / fold_right / fold_*_first

Constrained, range-native reductions (a proper accumulate).

Problem

`std::accumulate` is iterator-pair only, unconstrained, and not a ranges algorithm; no right-fold or first-element variants existed.

C++20 — before
std::accumulate(v.begin(), v.end(), 0, std::plus{});
C++23 — after
#include <algorithm>   // ranges::fold_*
#include <vector>
#include <print>

int main() {
  std::vector v{1, 2, 3, 4};
  int sum = std::ranges::fold_left(v, 0, std::plus{});            // 10
  // No init: uses the first element, returns optional<T>
  auto maxv = std::ranges::fold_left_first(v, [](int a, int b){
    return a > b ? a : b; });                                     // optional(4)
  std::println("{} {}", sum, *maxv);
}

Notes

  • Family: `fold_left`, `fold_right`, `fold_left_first`, `fold_right_first`, plus `*_with_iter` variants.
  • `*_first` versions return `optional` (empty range → nullopt) and need no init value.
  • Concept-constrained and range/projection aware — prefer over `std::accumulate` in new code.

Compiler support

GCCGCC 13
ClangClang 17 (libc++)
MSVCMSVC 19.37 / VS 17.7
Try on Compiler Explorer

C++26

C++26LanguageP2996DRAFT

Static reflection + code injection

Reflect on types at compile time and splice the results back into code.

Problem

C++ had no standard introspection: enum→string, struct field iteration, and serialization all relied on macros or external codegen.

C++26
#include <meta>     // header name still settling
#include <print>

enum class Color { red, green, blue };

template <typename E>
constexpr std::string_view name_of(E e) {
  // reflect each enumerator, match value, splice its identifier
  template for (constexpr auto m : std::meta::enumerators_of(^^E)) {
    if (e == [:m:]) return std::meta::identifier_of(m);
  }
  return "?";
}

int main() { std::println("{}", name_of(Color::green)); } // green

Notes

  • DRAFT (P2996): voted into the C++26 working draft, but syntax/spelling is still being refined — verify against the latest paper revision.
  • `^^X` is the reflection operator (yields `std::meta::info`); `[: r :]` is the splice that turns a reflection back into code.
  • Enables enum↔string, struct serialization, and ORM-style mapping with zero macros. Toolchains ship it only behind experimental flags.

Compiler support

GCCnot yet
Clangexperimental fork / Clang 20+ flags
MSVCnot yet
Try on Compiler Explorer
C++26LanguageP2900DRAFT

Contracts: pre / post / contract_assert

First-class preconditions, postconditions, and assertions.

Problem

C++ had only `assert` (no pre/post distinction, no result access, no standard build modes); Contracts were pulled from C++20.

C++26
int divide(int a, int b)
  pre (b != 0)               // precondition
  post (r: r * b <= a)       // postcondition names the result 'r'
{
  contract_assert(a >= 0);   // in-body assertion
  return a / b;
}

Notes

  • DRAFT (P2900): merged into the C++26 working draft; exact semantics and the violation-handler API are still in flux.
  • Evaluation semantics are selectable at build time (ignore / observe / enforce / quick-enforce).
  • A contract violation invokes a (possibly user-installed) violation handler — richer than `assert`'s abort.
  • Distinct from `[[assume]]`: contracts CHECK; assume tells the optimizer to TRUST without checking.

Compiler support

GCCexperimental (-fcontracts, evolving)
Clangexperimental fork
MSVCnot yet
Try on Compiler Explorer
C++26LanguageP2662

Pack indexing: pack...[i]

Directly index a parameter pack or a structured-binding pack.

Problem

Getting the i-th element of a pack in C++20 required recursive templates or `std::tuple`/`get` gymnastics.

C++20 — before
template <class... Ts>
using first = std::tuple_element_t<0, std::tuple<Ts...>>; // verbose
C++26 — after
template <class... Ts>
constexpr auto first_value(Ts... vs) {
  return vs...[0];          // value pack indexing
}

template <class... Ts>
using first_type = Ts...[0];  // type pack indexing

int main() {
  static_assert(first_value(10, 20, 30) == 10);
  static_assert(sizeof(first_type<char, int>) == 1);
}

Notes

  • Index must be a constant expression; out-of-range is ill-formed.
  • Combines with C++26 structured bindings that introduce a pack (`auto [...xs] = tuple;`).

Compiler support

GCCGCC 15
ClangClang 19
MSVCnot yet
Try on Compiler Explorer
C++26LanguageP1061DRAFT

Structured bindings as a pack

Bind the remaining members of a tuple-like into one pack.

Problem

C++20 structured bindings needed one name per element; you couldn't capture 'the rest' generically.

C++26
#include <tuple>

template <class Tuple>
constexpr auto sum_tail(Tuple t) {
  auto [head, ...tail] = t;     // tail is a pack
  return (tail + ... + 0);      // fold over the pack
}

int main() {
  static_assert(sum_tail(std::tuple{1, 2, 3, 4}) == 9); // 2+3+4
}

Notes

  • DRAFT (P1061): in the C++26 working draft; confirm against the latest revision.
  • The introduced pack can be expanded, folded, or indexed with `tail...[i]`.
  • Works inside templates where the element count is dependent.

Compiler support

GCCGCC 15 (partial)
ClangClang 18 (partial)
MSVCnot yet
Try on Compiler Explorer
C++26LanguageP2573

=delete("reason")

Attach an explanatory message to a deleted function.

Problem

A plain `= delete` gives a terse diagnostic; users don't learn WHY an overload is forbidden or what to use instead.

C++20 — before
void f(double) = delete; // error message says nothing useful
C++26 — after
void send(int) { /* ... */ }
void send(double) = delete("use send(int) — doubles truncate silently");

int main() {
  send(1);
  // send(1.0); // error: call to deleted function: use send(int) ...
}

Notes

  • The string appears in the compiler diagnostic, guiding the caller to the right API.
  • Useful for poisoning dangerous implicit conversions and deprecated overloads.

Compiler support

GCCGCC 15
ClangClang 19
MSVCnot yet
Try on Compiler Explorer
C++26LanguageP2169

The _ placeholder variable

Introduce intentionally-unnamed, reusable bindings.

Problem

Unused locals/bindings triggered warnings, and `[[maybe_unused]]` everywhere is noisy; `_` couldn't be reused.

C++20 — before
auto [x, y, ignored] = get();        // 'ignored' warns if unused
std::lock_guard<std::mutex> lk(m);   // must name the guard
C++26 — after
#include <mutex>
std::mutex m;

void f() {
  auto [x, _, _] = std::tuple{1, 2, 3};  // multiple _ in one decl: OK
  std::lock_guard _(m);                  // RAII guard you never touch
  // using '_' (reading it) is ill-formed if declared more than once
}

Notes

  • `_` can be declared repeatedly in the same scope without redefinition errors.
  • Reading a re-declared `_` is ill-formed; it's write-/discard-only.
  • Cleans up structured bindings, RAII guards, and unused lambda captures.

Compiler support

GCCGCC 15
ClangClang 18
MSVCnot yet
Try on Compiler Explorer
C++26LanguageP3068 / P2747 / P2786DRAFT

constexpr exceptions & placement new; trivial relocation

Constant evaluation gets try/throw and placement new; types can opt into bitwise relocation.

Problem

C++23 still banned `throw`/`try` and placement-new during constant evaluation, and moving objects always ran a move ctor + destructor even when a memcpy would do.

C++26
// constexpr exceptions (P3068):
constexpr int checked(int x) {
  if (x < 0) throw "neg";   // throw allowed in constant evaluation (C++26)
  return x;
}
static_assert(checked(5) == 5);

// Trivial relocation (P2786): opt a type in, enabling memcpy-style moves
struct [[trivially_relocatable]] Buffer { /* ... */ };

Notes

  • DRAFT: these are separate papers at different maturity — verify each against the latest working draft; the relocation spelling in particular is still settling.
  • constexpr placement new (P2747) lets you build allocator-aware constexpr containers.
  • Trivial relocation lets containers relocate elements with a memcpy, skipping move-ctor/dtor pairs (huge for vector growth).

Compiler support

GCCpartial / experimental
Clangpartial / experimental
MSVCnot yet
Try on Compiler Explorer
C++26LibraryP0843

std::inplace_vector

A vector with fixed capacity and zero heap allocation.

Problem

C++ had `array` (fixed size) and `vector` (heap). There was no standard contiguous container with dynamic size up to a static cap — embedded/real-time code used custom types.

C++26
#include <inplace_vector>

int main() {
  std::inplace_vector<int, 4> v;   // capacity 4, all storage inline
  v.push_back(1);
  v.push_back(2);
  v.emplace_back(3);
  // v.push_back fails (throws bad_alloc) once size() == 4
  // try_push_back returns nullptr instead of throwing
}

Notes

  • Storage lives inside the object — no allocator, no heap, trivially usable in constexpr and freestanding.
  • Exceeding capacity throws `bad_alloc`; use `try_push_back` / `unchecked_push_back` for non-throwing / unchecked paths.
  • Ideal for hot loops, embedded, and lock-free scratch buffers where allocation is forbidden.

Compiler support

GCCGCC 15 (partial)
Clanglibc++ not yet shipped
MSVCpartial
Try on Compiler Explorer
C++26LibraryP0543

Saturation arithmetic: add_sat, sub_sat, ...

Clamp on overflow instead of wrapping or invoking UB.

Problem

C++20 integer overflow was UB (signed) or modular wraparound (unsigned); clamping required manual checks easy to get wrong.

C++26
#include <numeric>   // saturation ops
#include <cstdint>

int main() {
  std::uint8_t a = std::add_sat<std::uint8_t>(250, 10); // 255, not 4
  std::int8_t  b = std::sub_sat<std::int8_t>(-120, 50); // -128, not overflow
  auto c = std::saturate_cast<std::int8_t>(1000);       // 127
}

Notes

  • Family: `add_sat`, `sub_sat`, `mul_sat`, `div_sat`, and `saturate_cast<T>`.
  • Results clamp to the destination type's [min, max]; constexpr-friendly.
  • Great for DSP, graphics, and fixed-point pipelines where wraparound is a bug.

Compiler support

GCCGCC 14
ClangClang 18
MSVCMSVC 19.40 / VS 17.10
Try on Compiler Explorer
C++26LibraryP1673 / P2630DRAFT

std::linalg (BLAS interface) & submdspan

Standard dense linear algebra over mdspan, plus zero-copy slicing.

Problem

Numerical C++ had to bind to a vendor BLAS by hand; and C++23 `mdspan` could not be sliced into sub-views.

C++26
#include <linalg>
#include <mdspan>
#include <vector>
namespace la = std::linalg;

int main() {
  std::vector<double> a(9), b(3, 1.0), y(3, 0.0);
  std::mdspan A(a.data(), 3, 3);
  std::mdspan x(b.data(), 3), Y(y.data(), 3);

  la::matrix_vector_product(A, x, Y);   // Y = A * x (BLAS-2)

  // submdspan: a non-owning slice (here, the first row)
  auto row0 = std::submdspan(A, 0, std::full_extent);
}

Notes

  • DRAFT: `std::linalg` (P1673) is in the working draft; `submdspan` (P2630) targets C++26 — confirm both against the latest revision.
  • `linalg` is a thin, BLAS-shaped free-function interface over `mdspan`; layouts map to row/column-major BLAS calls.
  • `submdspan` slices with indices, `std::full_extent`, `std::strided_slice`, or tuples — no copy.

Compiler support

GCCnot yet
Clanglibc++ not yet shipped
MSVCsubmdspan partial
Try on Compiler Explorer
C++26ConcurrencyP2300DRAFT

std::execution — senders / receivers

A standard framework for structured asynchrony and parallelism.

Problem

C++ had no composable async model: futures don't compose, callbacks nest, executors stalled for years.

C++26
#include <execution>   // <stdexec> in the reference impl
namespace ex = std::execution;

int main() {
  // Build a lazy pipeline of work (a 'sender'):
  auto work = ex::just(21)
            | ex::then([](int x){ return x * 2; });   // transform

  // Launch on a scheduler and block for the result:
  auto [result] = std::this_thread::sync_wait(std::move(work)).value();
  // result == 42
}

Notes

  • DRAFT (P2300): voted into the C++26 working draft; the surface API and header/namespace are still being finalized — use the reference implementation (stdexec) today.
  • Senders describe work lazily; `then`, `let_value`, `when_all`, `bulk`, `transfer` compose pipelines that run on a chosen scheduler.
  • Designed to express CPU, GPU, and IO async uniformly with structured cancellation/error channels.

Compiler support

GCCnot yet (use stdexec)
Clangnot yet (use stdexec)
MSVCnot yet (use stdexec)
Try on Compiler Explorer
C++26ConcurrencyP2530 / P2545DRAFT

Hazard pointers & RCU

Standard safe-reclamation primitives for lock-free data structures.

Problem

Writing lock-free containers required home-grown reclamation (epoch/hazard schemes); the standard offered nothing for safe memory reclamation.

C++26
#include <hazard_pointer>   // header name per current draft

struct Node { int value; Node* next; };

int read(std::atomic<Node*>& head) {
  std::hazard_pointer h = std::make_hazard_pointer();
  Node* p = h.protect(head);   // p is safe to read until h is reset
  return p ? p->value : -1;
}

Notes

  • DRAFT (P2530 hazard pointers, P2545 RCU): in the C++26 working draft; APIs are still being finalized — verify spellings.
  • Hazard pointers: readers publish what they're using; reclaimers defer freeing until no hazard references remain.
  • RCU (read-copy-update): near-zero-cost reads, deferred reclamation — complementary to hazard pointers for different workloads.

Compiler support

GCCnot yet
Clangnot yet
MSVCnot yet
Try on Compiler Explorer