C++23 has some important features, but there are also exciting additions to the details.
Advertisement
As a reminder, here are the most important features of C++23.
Rainer Grimm has been working as a software architect, team and training manager for many years. He enjoys writing articles on the programming languages ​​C++, Python and Haskell, but also frequently speaking at specialist conferences. On his blog Modern C++ he discusses his passion C++ in depth.
Most important C++23 features
With this derivation, C++23 extends a small but very effective feature of the original language. Inferring this makes it possible to explicitly dereference the implicitly passed pointer in a member function definition, similar to Python. Thanks to this derivation, some complex techniques in C++, such as CRTP or the overload pattern, become child’s play.
The C++23 library gains several important additional features. You can use the standard library directly import std;
Import or C++20 format strings std::print
And std::println
Applying. We also get flat associative containers for performance reasons std::flat_map. std::flap_map
is a drop-in replacement for std::map. std::optional
This has been extended to include a monadic interface for compatibility reasons. New data types std::expected
It already has a composable interface and can store expected or unexpected values ​​for error handling. Thanks to std::mdspan
We get a multidimensional expansion. std::generator
Finally, the first concrete coroutine is for generating a stream of numbers. std::generator
is part of the range library, which has also been improved in C++23.
More details can be found in my previous articles:
original language
- C++23: Deducing makes this an explicit pointer
- C++23: syntactic sugar with minification
- C++23: Little pearls in the core language
- C++23: More little gems in the core language
Library
- C++23: A modular standard library and two new functions
- Categories: Improvements with C++23
- C++23: A new way of error handling with std::expected
- C++23: Four new associative containers
- C++23: A multidimensional view
But that’s not all that C++23 has to offer.
Fixed-width floating point types
As of now, C++ supports the following data types:
- no suffix: double
- F or f suffix: float
- l or l suffix: long double
C++23 introduces five new literal suffixes. The table on cppreference shows their data types and their properties:
The new data types are in the header
defined. You can use predefined macros to check whether your own implementation supports the new data types.
stack trace
Analyzing the current stack is often very useful for troubleshooting. The new StackTrace library offers exactly that.
The library has two classes:
stacktrace_entry:
Representation of evaluation in a stack tracebasic_stacktrace:
Snapshot of the entire stack trace or a specific part
In simple terms: with class stacktrace_entry
You can find information about the evaluation in the stack trace. Each stacktrace_entry
The object is either empty or represents an evaluation in the stack trace.
Class basic_stacktrace
A snapshot of the entire stack trace or a specific part of it.
Still confusing?
In the following simple example main
Implement the function func1
But, func1
Call func2
but and func2
Call func3
But.
// stacktrace1.cpp
#include
#include
void func3() {
std::cout << std::stacktrace::current() << '\n';
}
void func2() {
func3();
}
void func1() {
func2();
}
int main() {
func1();
}
func3
calls a static function current
But std::stacktrace
But. std::stacktrace
is a nickname for std::basic_stacktrace
With the standard allocator.
Provides information about the stack trace when the program is executed.
Compiling the program using the compiler explorer was quite a challenge. It took me a while to locate the appropriate GCC version and libraries.
Here is the command line for g++13.1: g++ -std=c++23 -lstdc++_libbacktrace
,
thanks to std::stacktrace_entry
The stack trace can be interrogated.
// stacktrace2.cpp
#include
#include
void func3() {
auto stacktrace = std::stacktrace::current();
for (const auto& entry: stacktrace) {
std::cout << "Description: " << entry.description() << '\n';
std::cout << "file: " << entry.source_file()
<< " and line: " << entry.source_line() <<'\n';
std::cout << '\n';
}
}
void func2() {
func3();
}
void func1() {
func2();
}
int main() {
func1();
}
At the ceremony func3
I loop through the stack trace entries and display their details, file and line clearly.
I will write only a few words about the remaining features of C++23. The examples come from cppreference.com.
I’d like to start with two characteristics that can affect a program’s performance.
std::unreachable
can do std::unreachable
Use to mark code that cannot be reached. Calling std::unreachable results in undefined behavior.
// from https://en.cppreference.com/w/cpp/utility/unreachable
switch (xy)
{
case 128: ((fallthrough));
case 256: ((fallthrough));
case 512: /* ... */
tex.clear();
tex.resize(xy * xy, Color{0, 0, 0, 0});
break;
default:
std::unreachable();
}
std::move_only_function
with C++11 std::function. std::function
is a polymorphic function wrapper and can accept arbitrary callable calls and give them a name. Callables are any entities that behave like functions, such as functions, function objects, or lambdas.
more about std::function
You can find out here: Functional in TR1 and C++11,
as a protest std::function
be able to std::move_only_function
Receive only one constructible callable. The callable object can contain std::move_only_function
To avoid additional storage requirements.
// from https://en.cppreference.com/w/cpp/utility/functional/move_only_function
auto lambda = (task = std::move(packaged_task))() mutable { task(); };
// std::function function = std::move(lambda); // Error
std::move_only_function function = std::move(lambda); // OK
std::byteswap
std::byteswap
Swaps the bytes in a value n. n
Sure Integrals Happen.
The following program applies std::byteswap
To.
// from https://en.cppreference.com/w/cpp/numeric/byteswap
#include
#include
#include
#include
#include
template<:integral t="">
void dump(T v, char term = '\n')
{
std::cout << std::hex << std::uppercase << std::setfill('0')
<< std::setw(sizeof(T) * 2) << v << " : ";
for (std::size_t i{}; i != sizeof(T); ++i, v >>= 8)
std::cout << std::setw(2)
<< static_cast(T(0xFF) & v) << ' ';
std::cout << std::dec << term;
}
int main()
{
static_assert(std::byteswap('a') == 'a');
std::cout << "byteswap for U16:\n";
constexpr auto x = std::uint16_t(0xCAFE);
dump(x);
dump(std::byteswap(x));
std::cout << "\nbyteswap for U32:\n";
constexpr auto y = std::uint32_t(0xDEADBEEFu);
dump(y);
dump(std::byteswap(y));
std::cout << "\nbyteswap for U64:\n";
constexpr auto z = std::uint64_t{0x0123456789ABCDEFull};
dump(z);
dump(std::byteswap(z));
}
Finally, here is the output of the program.
What will happen next?
In my next article I will discuss C++26.
(RME)