C++23: This and That

I have already written 10 posts about C++23. Today, I want to discuss the features I forgot in the first round.

As a short reminder, here are the main C++23 features.

Main C++23 Features

C++23 offers with deducing this a small but very impactful feature of the core language. Deducing this allows you, similar to Python, to make the implicitly passed this pointer in a member function definition explicit. Thanks to deducing this, a few complex techniques in C++, such as CRTP or the Overload Pattern, will become a piece of cake.

The C++23 library will get many impactful additions. You can directly import the standard library with import std;, or apply the C++20 format string in std::print and std::println. Additionally, we will get flat associative containers such as std::flat_map for performance reasons. std::flap_map is a drop-in replacement for std::map. std::optional interface is extended with a monadic interface for composability. The new data type std::expected already has a composable interface and can store an expected or an unexpected value for error handling. Thanks to std::mdspan, we will get a multidimensional span. Finally, std::generator is the first concrete coroutine for creating a stream of numbers. std::generator is part of the ranges library, which will also be improved in C++23.

If you need more details, read my previous posts.

Core Language

  1. C++23: Deducing This
  2. C++23: Syntactic Sugar with Deducing This
  3. C++23: The Small Pearls in the Core Language
  4. C++23: More Small Pearls

Library

  1. C++23: A Modularized Standard Library, std::print and std::println
  2. Range Improvements with C++23
  3. C++23: A New Way of Error Handling with std::expected
  4. C++23: A Multidimensional View
  5. C++23: Range Improvements and std::generator

This is not all.

Fixed Width Floating Point Types

So far, C++ supports the following floating point types:

  • no suffix: double
  • f or F suffix: float
  • l or L suffix: long double

With C++23, we will get five new literal suffixes.

Rainer D 6 P2 500x500

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    The table from cppreference shows their types and their properties.

    The new data types are defined in the header <stdfloat>. You can check with the corresponding predefined macro, if your implementation supports the new data types. Then the macro is 1.

    Stacktrace

    Analyzing the current call stack is often very useful for debugging. The new stack trace library supports precisely this.

    The library consists of two classes:

    • stacktrace_entry: representation of an evaluation in a stack trace
    • basic_stacktrace: a snapshot of the whole stack trace or its given part

    Let me put this in simpler words. The stacktrace_entry class lets you get information about an evaluation in a stack trace. Each stacktrace_entry object is either empty, or represents an evaluation in a stack trace.

    The basic_stacktrace class is a snapshot of the whole stack trace or its given part.

    Still not clear?

    In the following simple example, the main function invokes the func1 function, func1 invokes func2, and func2 invokes func3.

    // stacktrace1.cpp
    
    #include <iostream>
    #include <stacktrace>
    
     
    void func3() {
        std::cout << std::stacktrace::current()  << '\n';
    }
     
    void func2() {
        func3();
    }
     
    void func1() {
        func2(); 
    }
     
    int main() {
        func1();
    }
    

    func3 calls the static function current on std::stacktrace. std::stacktrace is an alias for std::basic_stacktrace with the default allocator.

    Executing the program gives you information about the stack trace.

    Compiling the program on the Compiler Explorer was pretty challenging. It took me a while to find the appropriate GCC version and the libraries.

    Here is the command line for g++ 13.1: g++ -std=c++23 -lstdc++_libbacktrace.

    Thanks to the std::stacktrace_entry, you can query the stack trace.

    // stacktrace2.cpp
    
    #include <iostream>
    #include <stacktrace>
    
     
    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();
    }
    

    In the function func3, I iterate through the stack trace entries and expressly display its description, file, and line.

    I will only write a few words about the remaining C++23 features. The examples are from cppreference.com.

    Let me start with two features which may affect the performance of your program.

    std::unreachable

    You can use std::unreachable it to mark code that isn’t reachable. When you call std::unreachable, you’ll get 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 we got std::function. std::function is a polymorphic function wrapper. Therefore, it can take arbitrary callables and give them a name.  Callables are all entities that behave like functions, such as functions, function objects, or lambdas.

    Read more about std::function here: Functional in TR1 and C++11.

    On the contrary to std::function, can std::move_only_function only get a constructible callable. The callable object may be stored in the std::move_only_function to avoid memory allocation.

    // from https://en.cppreference.com/w/cpp/utility/functional/move_only_function
    
    
    auto lambda = [task = std::move(packaged_task)]() mutable { task(); };
     
    //  std::function<void()> function = std::move(lambda); // Error
    std::move_only_function<void()> function = std::move(lambda); // OK
     
    

    std::byteswap

    std::byteswap swaps the bytes in the value n. n must be integral.

    The following program applies std::byteswap.

    // from https://en.cppreference.com/w/cpp/numeric/byteswap
    
    #include <bit>
    #include <concepts>
    #include <cstdint>
    #include <iomanip>
    #include <iostream>
     
    template<std::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<unsigned>(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’s Next?

    With my next post, I jump into C++26

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,