Skip to content

Commit 3eee379

Browse files
committed
General cleanup for the C++26 article
1 parent 0d7dff6 commit 3eee379

1 file changed

Lines changed: 50 additions & 48 deletions

File tree

_posts/2026-04-05-exploring-c++26.md

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,22 @@ on the fun and read the spec to see what new language features were added.
1414

1515
This article is more or less a list of findings I think will be useful for
1616
those of us who work in slightly more constrained environments, rather than
17-
an overall look at everything in the spec. I will mostly be looking into
18-
features that help solve real problems within the embedded world and tend to
19-
be the most annoying to maintain - userspace daemons, device-facing middleware,
20-
debug utilities, platform layers, etc.
17+
an overall look at everything in the spec. I'll try to look into features that
18+
help solve real problems within the embedded world and tend to be annoying to
19+
maintain (userspace daemons, debug utilities, platform layers, etc.), but some
20+
examples might be slightly esoteric to help convey the intent of the feature.
2121

2222
Before we begin, I should note the usual caveats. Those who are not used to
2323
reading ISO specs like the currently existing
2424
[C++26 draft spec](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/n5014.pdf)
2525
should probably start with the
2626
[cppreference page](https://en.cppreference.com/w/cpp/26.html).
27-
The cppreference page will get you right into the heart of what is being added,
28-
and if you need more information, you can cross-reference the ISO PDF for more
29-
detail. From there, make sure to always check the cppreference compiler support
30-
section to see what feature(s) are available to you. As of writing this, the
31-
[meta-clang](https://github.com/kraj/meta-clang/tree/scarthgap-clang20) repo
27+
The cppreference page will be very "to the point" in terms of of what is being
28+
added, and if you need more information, you can cross-reference the ISO PDF
29+
for more detail. From there, make sure to always check the cppreference
30+
compiler support section to see what feature(s) are available to you. As of
31+
writing this, the
32+
[meta-clang](https://github.com/kraj/meta-clang/tree/scarthgap-clang20) layer
3233
supports version 20,
3334
[openembedded-core](https://git.openembedded.org/openembedded-core/tree/meta/recipes-devtools/gcc)
3435
supports GCC 15.2. Pretty much all references will be from these links, so make
@@ -40,13 +41,10 @@ A lot of embedded C++ ends up looking older than it needs to because teams
4041
optimize for predictability and portability. That is a totally reasonable
4142
approach, especially if you need massive support across multiple toolchains.
4243
The downside is that codebases with homegrown abstractions, such as custom
43-
fixed-capacity containers, custom debug traps, custom overflow helpers, and
44-
such tend to become less predictable as time goes on. C++26 standardizes
45-
several of those missing pieces for us. For example, `std::inplace_vector`
46-
provides a bounded, contiguous, resizable container with compile-time fixed
47-
capacity. `<stdckdint.h>` provides checked integer arithmetic. `<debugging>`
48-
adds debugger detection and breakpoints. `<rcu>` and `<hazard_pointer>`
49-
standardize two major safe-reclamation approaches.
44+
containers, traps, overflow helpers, and other custom platform code tend to
45+
become less predictable as time goes on. C++26 standardizes several things that
46+
might help make these a bit more robust in nature without relying on
47+
potential UB. Let's jump into it.
5048

5149
## `std::execution`
5250

@@ -55,7 +53,7 @@ A service or daemon starts out very simple. It might start by reading a
5553
message, validating it, possibly transforming it, publishing it, and
5654
eventually updating some state. But then it grows threads, queues, timeouts,
5755
and more. Before long, the real logic is hidden under the sea of the
58-
synchronization hell, and becomes a bit of a massive pain to maintain.
56+
synchronization, and it becomes a bit of a massive pain to maintain.
5957

6058
Enter `std::execution` - the execution control library in C++26
6159
that standardizes a model for async execution around schedulers, senders,
@@ -74,7 +72,7 @@ workflow as a series of stages, allowing us to focus on the work itself.
7472

7573
Imagine a daemon that reads one frame from a device or socket, validates it,
7674
and then publishes it to the rest of the system. `std::execution` is perfect
77-
here as we can break each section up into individual stages, making it way
75+
here as we can break each section up into individual steps, making it way
7876
easier to test and reason about.
7977

8078
```cpp
@@ -143,18 +141,18 @@ time to [check it out](https://www.youtube.com/watch?v=7z9NNrRDHQU) as it is
143141
extremely fascinating...ambitious claims aside.
144142
145143
The design in [P2996](https://isocpp.org/files/papers/P2996R13.html) centers on
146-
reflection values (`std::meta::info`), the reflection operator `^^` (love calling
147-
this guy the "cat's ears operator"), compile-time queries through `consteval`
148-
metafunctions, and splicing reflected entities back into code.
149-
150-
The paper explicitly covers member enumeration and related introspection
151-
capabilities that make real compile-time structural queries possible which can
152-
be insanely powerful. However, for embedded Linux, the best way to think about
153-
reflection is probably not advanced metaprogramming, but rather a way to
154-
help delete duplicate metadata. A lot of platform code still defines the same
155-
thing twice - once as a C++ type, and again as a macro list, serializer table,
156-
or maybe something like a config schema. Reflection lets the type itself become
157-
the source of truth. Heck, even getting rid of legacy
144+
reflection values (`std::meta::info`), the reflection operator `^^` (love
145+
calling this guy the "cat's ears operator"), compile-time queries through
146+
`consteval`, and splicing reflected entities back into code.
147+
148+
The paper explicitly covers extremely powerful and complex paradigms such as
149+
member enumeration and related introspection capabilities. These make real
150+
compile-time structural queries possible which can be insanely powerful.
151+
However, for embedded Linux, the best way to think about reflection is
152+
probably as a way to help delete duplicate metadata. A lot of platform code
153+
still defines the same thing twice - once as a C++ type, and again as a macro
154+
list, serializer table, or maybe something like a config schema. Reflection
155+
lets the type itself become the source of truth. Heck, even getting rid of legacy
158156
[X-macro use cases](https://stackoverflow.com/questions/6635851/real-world-use-of-x-macros)
159157
would be a huge win alone.
160158
@@ -226,29 +224,30 @@ verbose=true
226224
All without having to write code for every field. Normally, you would write
227225
some sort of `std::string` to dump the config, but you are duplicating the
228226
struct manually, and adding fields later on requires you to update not only the
229-
struct, but also any of those helper functions.
230-
231-
Reflection is especially attractive anywhere you are doing that kind of work.
232-
Examples off the top of my head would be:
227+
struct, but also any of those helper functions. Reflection is especially
228+
attractive in those kinds of use cases. Examples off the top of my head would
229+
be:
233230

234231
- Config dumps (as shown above)
235232
- Serializer/deserializer glue code
236233
- ABI or layout verification helpers
237234

238-
Those are everywhere in our codebases, and we can drastically reduce places
239-
where type changes can silently drift from their associated glue code. This
240-
one does come with a bit of a cost, however. It is very easy to get into the
241-
weeds with insane template metaprogramming with how powerful these are, and
242-
you're right back to a readable mess that could have been solved with a simple
243-
X-macro. Use these with a bit of caution.
235+
Those tend to be everywhere in our codebases, and we can drastically reduce
236+
areas where type changes can silently drift from their associated glue code.
237+
238+
This one does come with a bit of a cost, however. It is very easy to get into
239+
the weeds with insane template metaprogramming given how powerful these are.
240+
If you're not used to reading more modern C++ code, there is also an argument
241+
that the X-macro alternative is more straightforward. Those are totally valid
242+
arguments, so you need to weigh maintenance with some readability and possibly
243+
modern C++ language training tradeoffs.
244244

245245
## `<hazard_pointer>` and `<rcu>`
246246

247247
Many platform daemons read things like:
248248

249249
- Subscription lists
250250
- Cached device state
251-
- Registries
252251
- Config snapshots
253252

254253
The hard part of making those structures concurrent is often not the swap
@@ -454,13 +453,16 @@ moves towards stronger correctness and safety expectations.
454453

455454
## `<stdckdint.h>`
456455

457-
C++26 adds `<stdckdint.h>` which includes checked integer arithmetic functions
458-
like `ckd_add`, `ckd_sub`, and `ckd_mul`. These perform integer arithmetic and
459-
report whether the result can be represented in the destination type. These are
460-
perfect for areas where overflow bugs show up like computing buffer offsets,
461-
conversions between units, total packet size, etc. With these being part of the
462-
standard library now, you can reduce the amount of fallbacks or
463-
compiler-specific builtins trying to do the same thing.
456+
C++26 adds `<stdckdint.h>` that was originally found in the
457+
[C23 standard](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf)
458+
which includes checked integer arithmetic functions like `ckd_add`, `ckd_sub`,
459+
and `ckd_mul`. These perform integer arithmetic and report whether the result
460+
can be represented in the destination type. These are perfect for areas where
461+
overflow bugs show up like computing buffer offsets, conversions between units,
462+
total packet size, etc. With these being part of the library now, you can
463+
reduce the amount of fallbacks or compiler-specific builtins trying to do the
464+
same thing. And since they were part of the C23 codebase, you can use them in
465+
your C codebases, as well!
464466

465467
### Compute total size of a packet
466468

0 commit comments

Comments
 (0)