New C++23 features I'm excited about
Work on c++23 standardisation is well in progress and we already have a couple of new features to play with. Toolchain support varies but some early testing is already possible. I’ve prepared a list of features that I, personally appreciate a lot and which most definitely will improve my code. Let’s go through them.
CppCon overview
There’s a set of great CppCon talks regarding new additions coming with c++23. Amongst others, a nice, concise and to the point overview is probably best presented by Marc Gregoire.
Features I like
Below is a short list of things, I think are definitely a good way forward.
P0881R7 - builtin support for stacktraces
This is something very much welcomed. A builtin, standardised support for stacktrace generation is a great tool when debugging crashes or software problems. I’m sure that this feature will be embraced by crash reporting software and CI systems as well to provide more details about potential problems.
Stacktraces, could be generated prior to raising an assertion in debug versions of the builds to provide more context to the bug. This will dramatically improve the debugging efforts as, most of the time, when investigating a crash or a bug, obtaining a backtrace is crucial to understand the nature of the problem.
It seems like the implementation is gonna be based on Boost.StackTrace which seems like a reasonable choice that will guarantee sufficient quality and robustness. Obtaining stacktraces will be as simple as:
|
|
How does it work in practice? I wasn’t able to verify the stacktrace library
so far. I’ve briefly tried with gcc 12.2.0 shipped with Arch Linux but, during
the compilation of a trivial example, the compiler core dumped (heh :)). Version of
clang
shipped with Arch Linux is complaining about problems within the
stacktrace library. This is something I definitely will verify once again.
|
|
Implementation status
- ❌ gcc 12.2.0 - crashes when building
- ❌ clang 14.0.6 - fails to build
P0288R9 std::move_only_function<>
This is a great addition. Prior to it, all function objects were copied when
passed as arguments. This also means that lambdas capturing move only objects
could not be passed around by value. This changes entirely with std::move_only_function<>
.
|
|
Implementation status
- ✔️ gcc 12.2.0
- ✔️ clang 14.0.6
P0323R12 std::expected
This is a new thing very similar to Result
object in rust. In a way,
std::expected
introduces new error handling paradigm. std::expected
wraps a return value and an error in case the returning function failed to
complete. Here’s a simple piece of code that demonstrates this in action:
|
|
There’s also value_or()
which allows to conveniently retrieve the expected
value or a provided default in case the std::expected
contains an error.
value()
called on an std::expected
containing an error will result in
std::bad_expected_access
being thrown.
It would be great to have a similar set of monadic operations for
std::expected
as proposed with std::optional
. Programmers with rust
background will also miss language syntax for error propagation. Still though,
it’s a great addition and definitely a step in the right direction.
Implementation status
- ✔️ gcc 12.2.0
- ✔️ clang 14.0.6
P2128R6 Multidimensional subscript operator
P2128R6 allows for arbitrary number of arguments to operator[]
. Which is a
great change. I’ve already read some articles calling operator[]
a new call
operator in disguise, since the semantics is now quite similar. Regardless,
this is a great change which most likely will simplify a lot of code
implementing operations on multidimensional data. In current C++ implementation most of the time, implementations relied on pairs of tuples to support multiple index arguments i.e:
|
|
With C++23 it’s gonna be a lot simpler:
|
|
And yes, you can have multiple overloads if required:
|
|
Implementation status
- ✔️ gcc 12.2.0
- ✔️ clang 14.0.6
P0330R8 std::size_t literals
New suffix literals for std::size_t
are a great small detail that allows to
avoid implicit conversion to int
. To be precise, the change introduces the
following suffixes:
z
/Z
for signed integer type corresponding tostd::size_t
uz
/zu
forstd::size_t
t
forstd::ptrdiff_t
ut
/tu
for unsigned integer type corresponding tostd::ptrdiff_t
This is something I see myself relying on very often. The examples in the proposal draft illustrate the need for these suffixes very clearly.
In case of std::min
, std::max
I can’t even count how many times I had to
static_cast
the constants or explicitly declare the function types. This can
now be avoided.
|
|
Same goes, when declaring more than one variable in for loop or initialisation expressions:
|
|
Implementation status
- ✔️ gcc 12.2.0
- ✔️ clang 14.0.6
P0627R6 std::unreachable()
This one is pretty self explanatory. It allows to explicitly mark unreachable code which often happens at the end of the function returning a value, like this one:
|
|
This should silence any potential “missing return value” warnings and, at least for me, makes the code more readable and self-documenting.
Implementation status
- ✔️ gcc 12.2.0
- ✔️ clang 14.0.6
P0798R6 Monadic std::optional operations
Oh no, we’ve got the “M” word. Hopefully, if you read my post about
functional parsing (which I strongly recommend) then you should have a
general understanding what a monad is (or rather what it does). I’m gonna be
bashed by this explanation but still gonna take the risk, in short, monadic
operation transforms the value category from one to another - in case of parser
combinators, this was a transformation from a parser of type “a” to a parser of
type “b” (Parser A -> Parser B). In case of std::optional
it will be a
transformation from std::optional<A>
to std::optional<B>
using a given
transformation function… and that’s what this change is all about.
We’ll now have the following new functions that allow to chain operations on optionals:
transform
and_then
or_else
transform
is purely monadic and meant to indeed transform from
std::optional<A>
-> std::optional<B>
.
and_then
- allows to compose/chain functions returning optionals
or_else
- allows to call a function when optional bears no value
Here’s an example:
|
|
Best thing about this approach is that the chain will be short-circuited should the optional contain no value at any stage.
Implementation status
- ✔️ gcc 12.2.0
- ✔️ clang 14.0.6
P1938R3 consteval conditions
This is a huge change which allows to conveniently bridge the build and run time execution. It’s now possible to explicitly check, if execution happens during build time and control the flow:
|
|
Implementation status
- ✔️ gcc 12.2.0
- ✔️ clang 14.0.6
P2216R3 std::format improvements & print header
This addition provides a new header <print>
with functions like:
std::print
std::println
Initially, I thought that it’s a bit gimmicky but actually this feels pretty cool. I’m about to find out in practice how good it works and with what caveats it comes but I see myself using these functions often.
Implementation status
- ❌ gcc 12.2.0
- ❌ clang 14.0.6
Features I don’t like
P2334R1 New preprocessor directives
This proposal adds two new pre-processor directives:
#elifdef
#elifndef
These are to simplify the ubiquitous:
#elif defined()
#elif !defined()
Honestly, I don’t see much value in that and considering the fact that there were hopes to deprecate preprocessor use in C++ altogether I find these rather superfluous.
I’m worried as well that this will cause further code fragmentation and in order to provide as much portability as possible, we will avoid these anyway as much as we can.
Implementation status
- ✔️ gcc 12.2.0
- ✔️ clang 14.0.6
P0849R8 out_ptr
This change solves a real, existing problem (or rather inconvenience) in many code bases that interface with C APIs that take a pointer as a return value. Consider the following:
|
|
The question is how to interface that with smart pointers? The usual approach looks something like:
|
|
Granted, this isn’t the prettiest and requires an extra temporary pointer to adopt the interface between the linked list allocation API and the smart pointer. Therefore, it is proposed to introduce a wrapper that would perform all these steps behind the scenes, like so:
|
|
I find it… redundant. The name inout_ptr
is pretty bad IMHO and very poorly
conveys the intentions, additionally it hides the details unnecessarily which
from my point of view are quite important for code readability.
Implementation status
- ❌ gcc 12.2.0
- ❌ clang 14.0.6
Conclusion
C++23 brings a lot of great changes to the language. It’s definitely something I look forward to. I’m still waiting for the introduction of networking which is a piece of puzzle C++ is definitely missing out of the box.