Articles & Books

Writing Senders -- Lucian Radu Teodorescu

2025-05-08_16-35-43.pngIn the December issue of Overload [Teodorescu24], we provided a gentle introduction to senders/receivers, arguing that it is easy to write programs with senders/receivers. Then, in the February issue [Teodorescu25a], we had an article that walked the reader through some examples showing how senders/receivers can be used to introduce concurrency in an application. Both of these articles focused on the end users of senders/receivers. This article focuses on the implementer’s side: what does it take to implement senders?

Writing Senders

by Lucian Radu Teodorescu

From the article:

If people are just using frameworks based on std::execution, they mainly need to care about senders and schedulers. These are user-facing concepts. However, if people want to implement sender-ready abstractions, they also need to consider receivers and operation states – these are implementer-side concepts. As this article mainly focuses on the implementation of sender abstractions, we need to discuss these two concepts in more detail.

A receiver is defined in P2300 as “a callback that supports more than one channel” [P2300R10]. The proposal defines a concept for a receiver, unsurprisingly called receiver. To model this concept, a type needs to meet the following conditions:

  • It must be movable and copyable.
  • It must have an inner type alias named receiver_concept that is equal to receiver_t (or a derived type).
  • std::execution::get_env() must be callable on an object of this type (to retrieve the environment of the receiver).

A receiver is the object that receives the sender’s completion signal, i.e., one of set_value()set_error(), or set_stopped(). As explained in the December 2024 issue [Teodorescu24], a sender may have different value completion types and different error completion types. For example, the same sender might sometimes complete with set_value(int, int), sometimes with set_value(double), sometimes with set_error(std::exception_ptr), sometimes with set_error(std::error_code), and sometimes with set_stopped(). This implies that a receiver must also be able to accept multiple types of completion signals.

The need for completion signatures is not directly visible in the receiver concept. There is another concept that the P2300 proposal defines, which includes the completion signatures for a receiver: receiver_of<Completions>. A type models this concept if it also models the receiver concept and provides functions to handle the completions indicated by Completions. More details on how these completions look will be covered in the example sections.

Using Token Sequences to Iterate Ranges -- Barry Revzin

There was a StackOverflow question recently that led me to want to write a new post about Ranges. Specifically, I wanted to write about some situations in which Ranges do more work than it seems like they should have to. And then what we can do to avoid doing that extra work.

Using Token Sequences to Iterate Ranges

by Barry Revzin

From the article:

The Problem

For the purposes of this post, I’m just going to talk about the very simple problem of:

for (auto elem : r) { 
use(elem); 
} 

In the C++ iterator model, this desugars into something like:

auto __it = r.begin(); 
auto __end = r.end(); 

while (__it != __end) { 
use(*__it); 

++__it; 
} 

I used a while loop here deliberately, because it’s a simpler construct and it lets me write the advance step last.

Now, if you want to customize the behavior of a range, those are your entry points right there. You can change what the initialization phase does (begin() and end()), you can change the check against completeness (__it != __end), you can change the read operation (*__it), and you can change the advance operation (++__it). That’s it. You can’t change the structure of the loop itself.

That alone is enough to offer a pretty large wealth of functionality. It’s a very powerful abstraction.

free performance: autobatching in my SFML fork -- Vittorio Romeo

This article shows how a very simple automatic batching strategy can be applied on top of SFML without affecting the library API, resulting in free performance for the end user!

free performance: autobatching in my SFML fork

by Vittorio Romeo

From the article:

In one of my previous articles, I discussed the design and implementation of the batching system in my fork of SFML. Wouldn’t it be nice if drawables were automatically batched, whenever possible?

AoS vs SoA in practice: particle simulation -- Vittorio Romeo

This article presents a practical benchmark of a particle simulation using both the AoS (Array of Structures) and SoA (Structure of Arrays) data layouts. How much performance can we gain by merely switching up the way our data is stored?

AoS vs SoA in practice: particle simulation

by Vittorio Romeo

From the article:

The benchmark simulates a large number of 2D particles that continuously change position, scale, opacity, and rotation. Through an ImGui-based UI1, you can choose the number of particles, toggle multithreading, and switch between AoS and SoA on the fly.

A demo is worth a thousand words, and since my fork of SFML supports Emscripten, you can try the benchmark directly in your browser. Play around with all the options – I’m curious to hear what results you get! [...]

Speeding up C++ Code with Template Lambdas -- Daniel Lemire

image-16-825x510.jpgInteger division is one of the most expensive operations in C++, but when the divisor is known at compile time, the compiler can optimize it significantly. This post explores different approaches—using templates, lambda expressions, and template metaprogramming—to speed up division while maintaining clean and efficient code.

Speeding up C++ Code with Template Lambdas

by Daniel Lemire

From the article:

Let us consider a simple C++ function which divides all values in a range of integers:

void divide(std::span<int> i, int d) {
 for (auto& value : i) {
 value /= d;
 }
}

A division between two integers is one of the most expensive operations you can do over integers: it is much slower than a multiplication which is, in turn, more expensive than an addition. If the divisor d is known at compile-time, this function can be much faster. E.g., if d is 2, the compiler might optimize away the division and use a shift and a few cheap instructions instead. The same is true with all compile-time constant: the compiler can often do better knowing the constant. (See Lemire et al., Integer Division by Constants: Optimal Bounds, 2021)

C++26: Removing Language Features -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGC++ is often seen as an ever-growing language, with each new standard introducing powerful features while maintaining backward compatibility. However, C++26 takes a step toward simplification by officially removing deprecated features, including implicit arithmetic conversions for enumerations and direct comparisons of C-style arrays, both of which previously led to unintended behavior.

C++26: Removing Language Features

by Sandor Dargo

From the article:

Probably you all heard that C++ is an ever-growing language - I wrote so many times as well. Each standard indeed comes with a great bunch of highly-anticipated features. At the same time, due to binary compatibility considerations, very few old features are removed. This has several implications:

  • we have probably more than one way to do something
  • the standard keeps growing

This is true, but let’s not forget that each new standard removes some features. In this post, let’s review what are the language features that are removed in C++26 and in a later post, we’ll have a look at the removed library features.

At this point, it’s worth mentioning that a removal from the language usually happens in two steps. First a feature gets deprecated, meaning that its users would face compiler warnings for using deprecated features. As a next step, which in some cases never comes, the compiler support is finally removed.

 

Bjarne Stroustrup on How He Sees C++ Evolving -- David Cassel

27c57b7a-colorized-bjarne_stroustrup_2013-by-victor-zavyalov-icpcnews-creative-commons-via-wikipedia-copy-1024x683.jpgBjarne Stroustrup, creator of C++, is advocating for the adoption of guideline-enforcing profiles to enhance the language's safety and security.

Bjarne Stroustrup on How He Sees C++ Evolving

by David Cassel

From the article:

“I wanted to educate the community at large, and members of WG21 in particular — on my views of C++’s intended direction of evolution,” Bjarne Stroustrup told TNS.

The 74-year-old creator of C++ has spent 40 years of his life watching the growth of the language he designed back in 1985.

To encourage some long-desired features, last month in Communications of the ACM Stroustrup published “21st Century C++“, a 6,300-word article promising to show “key concepts” for modern, type-safe “21st-century C++” to create “C++ on steroids”. For example, in the article Stroustrup highlighted long-standing experiments in approaches like writing safer code with guideline-enforcing profiles. To maintain compatibility with decades of already-written C++ code, “We can’t change the language,” Stroustrup writes. “But we can change the way it is used…”

Yet that evolution isn’t entirely up to him. In a section towards the end, Stroustrup acknowledges WG21, the standardization working group, and how it will inevitably play a role in how much the language can change. 

Details of std::mdspan from C++23 -- Bartlomiej Filipek

right_left_layout.pngIn this article, we’ll see details of std::mdspan, a new view type tailored to multidimensional data. We’ll go through type declaration, creation techniques, and options to customize the internal functionality.

Details of std::mdspan from C++23

by Bartlomiej Filipek

From the article:

In this article, we’ll see details of std::mdspan, a new view type tailored to multidimensional data. We’ll go through type declaration, creation techniques, and options to customize the internal functionality.

Type declaration 

The type is declared in the following way:

template<  
     class T,  
     class Extents,  
     class LayoutPolicy = std::layout_right,  
     class AccessorPolicy = std::default_accessor<T> 
> class mdspan; 

And it has its own header <mdspan>.

The main proposal for this feature can be found at https://wg21.link/P0009

Following the pattern from std::span, we have a few options to create mdspan: with dynamic or static extent.

The key difference is that rather than just one dimension, we can specify multiple. The declaration also gives more options in the form of LayoutPolicy and AccessorPolicy. More on that later.

C++26: No More UB in Lexing -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGUndefined behavior in C++ is a well-known source of headaches for developers, but surprisingly, even the lexing process contained cases of it—until now. Thanks to P2621R3 by Corentin Jabot, unterminated strings, macro-generated universal character names, and spliced UCNs are now formally defined, aligning the standard with real-world compiler behavior.

C++26: No More UB in Lexing

by Sandor Dargo

From the article:

If you ever used C++, for sure you had to face undefined behaviour. Even though it gives extra freedom for implementers, it’s dreaded by developers as it may cause havoc in your systems and it’s better to avoid it if possible.

Surprisingly, even the lexing process in C++ can result in undefined behaviour. Thanks to Corentin Jabot’s work and his P2621R3 that won’t be the case anymore. As it was accepted as a defect report starting from C++98, in fact, you benefit from this already if you use a new enough compiler.

Truth be told, compilers didn’t do any dangerous. They handled the below cases safely and deterministically. So this change is really about updating the standard and matching implementers’ work.

Let’s quickly see the three cases.

Unterminated strings

     // unterminated string used to be UB
     const char * foo = "

Who would have thought that an unterminated string or a character was UB?! Despite the permissive standard, all major compilers identified it as ill-formed. From now on, even the standard says so.
 

On Trying to Log an Exception as it Leaves your Scope -- Raymond Chen

RaymondChen_5in-150x150.jpgA customer attempted to log exceptions using a scope_exit handler, expecting it to capture and process exceptions during unwinding. However, they encountered crashes because ResultFromCaughtException requires an actively caught exception, which isn’t available during unwinding—leading to an unexpected termination.

On Trying to Log an Exception as it Leaves your Scope

by Raymond Chen

From the article:

A customer wanted to log exceptions that emerged from a function, so they used the WIL scope_exit object to specify a block of code to run during exception unwinding.

void DoSomething()
{
    auto logException = wil::scope_exit([&] {
        Log("DoSomething failed",
            wil::ResultFromCaughtException());
    });

    ⟦ do stuff that might throw exceptions ⟧

    // made it to the end - cancel the logging
    logException.release();
}

They found, however, that instead of logging the exception, the code in the scope_exit was crashing.

They debugged into the Result­From­Caught­Exception function, which eventually reaches something like this:

try
{
    throw;
}
catch (⟦ blah blah ⟧)
{
    ⟦ blah blah ⟧
}
catch (⟦ blah blah ⟧)
{
    ⟦ blah blah ⟧
}
catch (...)
{
    ⟦ blah blah ⟧
}

The idea is that the code rethrows the exception, then tries to catch it in various ways, and when it is successful, it uses the caught object to calculate a result code.