Articles & Books

Reflection voted into C++26: "Whole new language" -- Herb Sutter

The first trip report from the Sofia meeting is available:

Trip report: June 2025 ISO C++ standards meeting (Sofia, Bulgaria)

by Herb Sutter

From the article:

c26-reflection.pngA unique milestone: “Whole new language”

Today marks a turning point in C++: A few minutes ago, the C++ committee voted the first seven (7) papers for compile-time reflection into draft C++26 to several sustained rounds of applause in the room. I think Hana “Ms. Constexpr” Dusíková summarized the impact of this feature best a few days ago, in her calm deadpan way… when she was told that the reflection paper was going to make it to the Saturday adoption poll, she gave a little shrug and just quietly said: “Whole new language.”

Mic drop.

C++20 Concepts for Nicer Compiler Errors -- Daniel Lemire

image-33-825x510.jpgTemplates are one of C++’s most powerful features, enabling developers to write generic, reusable code—but they come with a cost: notoriously verbose and opaque error messages. With the introduction of concepts in C++20, we can now impose clear constraints on template parameters and get far more helpful diagnostics when something goes wrong.

C++20 Concepts for Nicer Compiler Errors

by Daniel Lemire

From the article:

In C++, templates enable generic programming by allowing functions and classes to operate on different data types without sacrificing type safety. Defined using the template keyword, they let developers write reusable, type-agnostic code, such as functions (e.g., template <typename T> max(T a, T b)) or classes (e.g., std::vector), where the type T is specified at compile time.

Historically, the C++ language has tended to produce complicated compiler error messages. The main culprit is template metaprogramming. C++ templates are powerful but complex. When errors occur in template code, the compiler generates long, verbose messages with nested type information, often involving deep template instantiations. A simple mistake in a template function can produce a message spanning multiple lines with obscure type names.

Let us consider an example. In C++, we often use the ‘Standard Template Library (STL)’. It includes a useful dynamic array template: std::vector. A vector manages a sequence of elements with automatic memory handling and flexible sizing. Unlike fixed-size arrays, it can grow or shrink at runtime through operations like push_back to append elements or pop_back to remove them. You can store just about anything in an std::vector but there are some limits. For example, your type must be copyable.

C++26: More constexpr in the Standard Library -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGHere are the standard library features that will soon be usable at compile time. One topic is missing: exceptions. As they need both core language and library changes, I thought they deserved their own post.

C++26: More constexpr in the Standard Library

by Sandor Dargo

From the article:

P2562R1constexpr stable sorting

This paper proposes making std::stable_sortstd::stable_partitionstd::inplace_merge, and their ranges counterparts usable in constant expressions. While many algorithms have become constexpr over the years, this family related to stable sorting had remained exceptions — until now.

The recent introduction of constexpr containers gives extra motivation for this proposal. If you can construct a container at compile time, it’s only natural to want to sort it there, too. More importantly, a constexpr std::vector can now support efficient, stable sorting algorithms.

A key question is whether the algorithm can meet its computational complexity requirements under the constraints of constant evaluation. Fortunately, std::is_constant_evaluated() provides an escape hatch for implementations. For deeper details, check out the proposal itself.

Fixing exception safety in our task_sequencer -- Raymond Chen

RaymondChen_5in-150x150.jpgSome time ago, we developed a task_sequencer class for running asynchronous operations in sequence. There’s a problem with the implementation of Queue­Task­Async: What happens if an exception occurs?

Fixing Exception Safety in our task_sequencer

by Raymond Chen

From the article:

Let’s look at the various places an exception can occur in Queue­Task­Async.

    template<typename Maker>
    auto QueueTaskAsync(Maker&& maker) ->decltype(maker())
    {
        auto current = std::make_shared<chained_task>();
        auto previous = [&]
        {
            winrt::slim_lock_guard guard(m_mutex);
            return std::exchange(m_latest, current); ← oops
        }();

        suspender suspend;

        using Async = decltype(maker());
        auto task = [](auto&& current, auto&& makerParam,
                       auto&& contextParam, auto& suspend)
                    -> Async
        {
            completer completer{ std::move(current) };
            auto maker = std::move(makerParam);
            auto context = std::move(contextParam);

            co_await suspend;
            co_await context;
            co_return co_await maker();
        }(current, std::forward<Maker>(maker),
          winrt::apartment_context(), suspend);

        previous->continue_with(suspend.handle);

        return task;
    }

If an exception occurs at make_shared, then no harm is done because we haven’t done anything yet.

If an exception occurs when starting the lambda task, then we are in trouble. We have already linked the current onto m_latest, but we will never call continue_with(), so the chain of tasks stops making progress.

To fix this, we need to delay hooking up the current to the chain of chained_tasks until we are sure that we have a task to chain. 

How to Join or Concat Ranges, C++26 -- Bartlomiej Filipek

howtojoinconcat-filipek.pngC++ continues to refine its range library, offering developers more efficient and expressive ways to manipulate collections. In this post, we'll dive into three powerful range adaptors—concat_view, join_view, and join_with_view—exploring their differences, use cases, and practical examples.

How to Join or Concat Ranges, C++26

by Bartlomiej Filipek

From the article:

Modern C++ continuously improves its range library to provide more expressive, flexible, and efficient ways to manipulate collections. Traditionally, achieving tasks like concatenation and flattening required manual loops, copying, or custom algorithms. With C++’s range adaptors, we now have an elegant and efficient way to process collections lazily without unnecessary allocations.

In this post, we will explore three powerful range adaptors introduced in different C++ standards:

  • std::ranges::concat_view (C++26)
  • std::ranges::join_view (C++20)
  • std::ranges::join_with_view (C++23)

Let’s break down their differences, use cases, and examples.

std::ranges::concat_view (C++26)

The concat_view allows you to concatenate multiple independent ranges into a single sequence. Unlike join_view, it does not require a range of ranges—it simply merges the given ranges sequentially while preserving their structure.

In short:

  • Takes multiple independent ranges as arguments.
  • Supports random access if all underlying ranges support it.
  • Allows modification if underlying ranges are writable.
  • Lazy evaluation: No additional memory allocations.

Function overloading is more flexible than template function specialization -- Raymond Chen

RaymondChen_5in-150x150.jpgWhen trying to specialize a templated function for specific types, it’s easy to fall into subtle traps around how parameter types are matched. A colleague recently ran into this issue while attempting to specialize a function for a Widget and a string literal, only to be met with confusing compiler errors that hinted at deeper quirks in C++'s type deduction and function template specialization rules.

Function overloading is more flexible (and more convenient) than template function specialization

by Raymond Chen

From the article:

A colleague of mine was having trouble specializing a templated function. Here’s a simplified version.

template<typename T, typename U>
bool same_name(T const& t, U const& u)
{
    return t.name() == u.name();
}

They wanted to provide a specialization for the case that the parameters are a Widget and a string literal.

template<>
bool same_name<Widget, const char[]>(Widget const& widget, const char name[])
{
    return strcmp(widget.descriptor().name().c_str(), name) == 0;
}

However, this failed to compile:

// msvc
error C2912: explicit specialization 'bool 
same_name<Widget,const char[]>(const Widget &,const char 
[])' is not a specialization of a function template

What do you mean “not a specialization of a function template”? I mean doesn’t it look like a specialization of a function template? It sure follows the correct syntax for a function template specialization.

C++26: More constexpr in the Core Language -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGIn this article, we review how constexpr evolves in the C++26 core language. We are getting constexpr cast from void*, placement new, structured bindings and even exceptions (not discussed today). 

C++26: More constexpr in the Core Language

by Sandor Dargo

From the article:

Since constexpr was added to the language in C++11, its scope has been gradually expanded. In the beginning, we couldn’t even use ifelse or loops, which were changed in C++14. C++17 added support for constexpr lambdas. C++20 added the ability to use allocation and use std::vector and std::string in constant expressions. In this article, let’s see how constexpr evolves with C++26. To be more punctual, let’s see what language features become more constexpr-friendly. We’ll discuss library changes in a separate article, as well as constexpr exceptions, which need both language and library changes.

P2738R1constexpr cast from void*

Thanks to the acceptance of P2738R1, starting from C++26, one can cast from void* to a pointer of type T in constant expressions, if the type of the object at that adress is exactly the type of T.

Note that conversions to interconvertible - including pointers to base classes - or not related types are not permitted.

The motivation behind this change is to make several standard library functions or types work at compile time. To name a few examples: std::formatstd::functionstd::function_refstd::any. The reason why this change will allow many more for more constexpr in the standard library is that storing void* is a commonly used compilation firewall technique to reduce template instantiations and the number of symbols in compiled binaries.

Streamlined Iteration: Exploring Keys and Values in C++20 -- Daniel Lemire

image-27-825x510.jpgModern C++ offers a variety of ways to work with key-value data structures like std::map and std::unordered_map, from traditional loops to sleek functional-style expressions using C++20 ranges. By exploring both styles and benchmarking them across platforms, we can better understand how newer language features affect readability, expressiveness, and performance.

Streamlined Iteration: Exploring Keys and Values in C++20

by Daniel Lemire

From the article:

In software, we often use key-value data structures, where each key is unique and maps to a specific value. Common examples include dictionaries in Python, hash maps in Java, and objects in JavaScript. If you combine arrays with key-value data structures, you can represent most data.

In C++, we have two standard key-value data structures: the std::map and the std::unordered_map. Typically, the std::map is implemented as a tree (e.g., a red-black tree) with sorted keys, providing logarithmic time complexity O(log n) for lookups, insertions, and deletions, and maintaining keys in sorted order. The std::unordered_map is typically implemented as a hash table with unsorted keys, offering average-case constant time complexity O(1) for lookups, insertions, and deletions. In the std::unordered_map, the hash table uses a hashing function to map keys to indices in an array of buckets. Each bucket is essentially a container that can hold multiple key-value pairs, typically implemented as a linked list. When a key is hashed, the hash function computes an index corresponding to a bucket. If multiple keys hash to the same bucket (a collision), they are stored in that bucket’s linked list.

Quite often, we only need to look at the keys, or look at the values. The C++20 standard makes this convenient through the introduction of ranges (std::ranges::views::keys and std::ranges::views::values). Let us consider two functions using the ‘modern’ functional style. The first function sums the values and the next function counts how many keys (assuming that they are strings) start with a given prefix.

Owning and non-owning C++ Ranges -- Hannes Hauswedell

This is the first article in a series discussing some of the underlying properties of C++ ranges and in particular range adaptors. At the same time, I introduce the design of an experimental library which aims to solve some of the problems discussed here.

Owning and non-owning C++ Ranges

by Hannes Hauswedell

From the article:

We will begin by having a look at ranges from the standard library prior to C++20, since this is what people are most used to. Note that although the ranges themselves are from C++17, I will use some terminology/concepts/algorithms introduced later to explain how they relate to each other. Remember that to count as a range in C++, a type needs to have just begin() and end(). Everything else is bonus.

[…]

Containers are the ranges everybody already used before Ranges were a thing. They own their elements, i.e. the storage of the elements is managed by the container and the elements disappear when the container does. Containers are multi-pass ranges, i.e. you can iterate over them multiple times and will always observe the same elements.

[…]

If containers are owning ranges, what are non-owning ranges? C++17 introduced a first example: std::string_view, a range that consists just of a begin and end pointer into another range’s elements.

[…]

However, the most important (and controversial) change came by way of P2415, which allowed views to become owning ranges. It was also applied to C++20 as a defect report, although it was quite a significant design change. This is a useful feature, however, it resulted in the std::ranges::view concept being changed to where it no longer means “non-owning range”.

[…]

 

 

How to break or continue from a lambda loop? -- Vittorio Romeo

Is it possible to write a simple iteration API that hides implementation details and lets users break and continue?

Here's a new article about a lightweight solution using a `ControlFlow` enumeration!

How to break or continue from a lambda loop?

by Vittorio Romeo

From the article:

Here’s an encapsulation challenge that I frequently run into: how to let users iterate over an internal data structure without leaking implementation details, but still giving them full control over the loop?

Implementing a custom iterator type requires significant boilerplate and/or complexity, depending on the underlying data structure.

Coroutines are simple and elegant, but the codegen is atrocious – definitely unsuitable for hot paths.