If my math is correct, there are 125 changes / fixes / new features in C++23 and we are progressively covering them on this blog. I try to go from topic to topic. There are some topics with many smaller changes, such as
constexpr, there are some significant topics where even one topic must be in its own post such as the stacktrace library, and there are also some shorter posts with few and quite small changes on a given topic. Today we are going to discuss
noexcept related changes.
If you haven't read it, I'd recommend reading my article on
noexceptand its effects on binary size
We can definitely see two trends in the proposals accepted for the latest standards. They try to make more and more functions:
The two main changes presented today fit into this trend. But let's start with discussing a bit of
noexcept policies in the standard.
The current policies on whether something in the standard can be
noexcept or not is defined by P1656R2. Let me summarize the gist of it here.
As we got used to it, a destructor should never throw and therefore even if you don't mark it
noexcept they implicitly are!
There might be standard library functions marked unconditionally
noexcept if the committee fully agrees that the given function cannot throw.
The standard specifies a couple of special member functions and library functions that can be marked conditionally
noexcept, based on the underlying types and the types these functions operate on. Nothing else can be marked
noexcept, except for library functions that are designed for compatibility with C. Those can be unconditionally
Most often, we mark functions either
noexceptor not. So we often mark our functions unconditionally
noexceptcan take a compile-time computable condition, such as
std::is_nothrow_move_constructible_v<T> && std::is_nothrow_assignable_v<T&, U>
Here is the list that can be marked conditionally
noexcept according to C++20.
- the copy-constructor and -assignment operator
- the move-constructor and -assignment operator
This list is getting modified in the C++23 standard.
It's also worth noting that an implementation can mark conditionally
noexcept a function even if it's not listed by the standard so.
One of the primary use cases for
std::exchange is implementing the move constructor and move assignment operator. In a certain way, it's quite similar to
std::swap. Yet, while
std::swap and move operations can be conditionally
noexcept, it was not the case for
Up until C++23.
noexcept. The conditions are the same as for move operations:
is_nothrow_move_constructible_v<T> && is_nothrow_assignable_v<T&, U>.
With the introduction of
zip algorithms in C++23, people in and around the committee started to talk once again more and more about
The reason is that
std::apply could be effectively used to implement these new algorithms. In fact, in the previously referenced proposal
apply appears quite a few times. Sadly,
std::apply is not
But, if we have a look into the exposition-only implementation of
apply, we can see that it uses
get. The latter is
noexcept and the former is conditionally
noexcept, so there is no reason why
std::apply should not be conditionally
And that becomes the new reality with C++23:
template<class F, class Tuple> constexpr decltype(auto) apply(F&& f, Tuple&& t) noexcept(see below); // Let I be the pack 0, 1, ..., (tuple_size_v<remove_reference_t<Tuple>>-1). The exception specification is equivalent to: noexcept(invoke(std::forward<F>(f), get<I>(std::forward<Tuple>(t))...)).
In this article, we reviewed how the standard defines its policies towards the
noexcept specification and we also see that two standard library functions (
std::exchange) are becoming
If you liked this article, please