TimelineCpp20CoreLanguage

volatile and Other Small Improvements in C++20

Today, I complete my tour through the C++20 core language features with a few small improvements. One interesting of these minor improvements is that most of volatile has been deprecated.

 TimelineCpp20CoreLanguage

volatile

The abstract in the proposal P1152R0 briefly describes the changes that volatile undergoes: “The proposed deprecation preserves the useful parts of volatile, and removes the dubious / already broken ones. This paper aims at breaking at compile-time code which is today subtly broken at runtime or through a compiler update.

Before I show you what semantic of volatile is preserved, I want to start with the deprecated features:

  1. Deprecate volatile compound assignment, and pre/post increment/decrement
  2. Deprecate volatile qualification of function parameters or return types
  3. Deprecate volatile qualifiers in a structured binding declaration

If you want to know all the sophisticated details, I strongly suggest you watch the CppCon 2019 talk “Deprecating volatile” from JF Bastien. Here are a few examples from the talk referring to used numbers (1) to (3).

 

(1)
int neck, tail;
volatile int brachiosaur;
brachiosaur = neck;   // OK, a volatile store
tail = brachiosaur;    // OK, a volatile load

// deprecated: does this access brachiosaur once or twice
tail = brachiosaur = neck;

// deprecated: does this access brachiosaur once or twice
brachiosaur += neck;

// OK, a volatile load, an addition, a volatile store
brachiosau = brachiosaur + neck;

#########################################
(2)
// deprecated: a volatile return type has no meaning
volatile struct amber jurassic();

// deprecated: volatile parameters aren't meaningful to the
//             caller, volatile only applies within the function
void trex(volatile short left_arm, volatile short right_arm);

// OK, the pointer isn't volatile, the data is opints to is
void fly(volatile struct pterosaur* pterandon);

########################################
(3)
struct linhenykus { volatile short forelimb; };
void park(linhenykus alvarezsauroid) {
    // deprecated: doe the binding copy the foreelimbs?
    auto [what_is_this] = alvarezsauroid;
    // ...
}

 

I didn’t answer the crucial question: When should you use volatile?  A note from the C++ standard says that “volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation.” This means for a single thread of execution that, the compiler must perform load or store operations in the executable as often as they occur in the source code. volatile operations, therefore, cannot be eliminated or reordered. Consequently, you can use volatile objects to communicate with a signal handler but not with another thread of execution.

To summarize, volatile avoids aggressive optimization and has no multithreading semantics.

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.

     

    I present the remaining minor improvements with a short example that runs in the Compiler Explorer.

    Range-based for-loop with Initializers

    With C++20, you can directly use a range-based for-loop with an initializer.

     

    // rangeBasedForLoopInitializer.cpp
    
    #include <iostream>
    #include <vector>
    
    int main() {
    
        for (auto vec = std::vector{1, 2, 3}; auto v : vec) {  // (1)
            std::cout << v << " ";
        }
        
        std::cout << "\n\n";
        
        for (auto initList = {1, 2, 3}; auto e : initList) {  // (2)
            e *= e;
            std::cout << e << " ";
        }
        
        std::cout << "\n\n";
        
        using namespace std::string_literals;
        for (auto str = "Hello World"s; auto c: str) {        // (3)
            std::cout << c << " ";
        }
         
    }
    

     

    The range-based for-loop uses in line (1) a std::vector, in line (2) a std::initializer_list, and line (3) a std::string. Additionally, in lines (1) and line (2), I apply automatic type deduction for class templates we have had since C++17. Instead of std::vector<int> and std::initalizer_list<int>, I write std::vector and std::initializer_list

    With GCC 10.2 and Compiler Explorer, I get the expected output.

    rangeBasedForLoopWithInitializer 

    virtual constexpr Functions

    A constexpr function has the potential to run at compile-time but can also be executed at run-time. Consequently, you can make a constexpr function with C++20 virtual. Both directions are possible. Neither can a virtual constexpr function override a  non-constexpr function nor can a virtual non-constexpr function override a  constexpr virtual function. I want to emphasize that override implies that the regarding function of a base class is virtual

    The following program shows both combinations.

     

    // virtualConstexpr.cpp
    
    #include <iostream>
    
    struct X1 {
        virtual int f() const = 0;
    };
    
    struct X2: public X1 {
        constexpr int f() const override { return 2; }
    };
    
    struct X3: public X2 {
        int f() const override { return 3; }
    };
    
    struct X4: public X3 {
        constexpr int f() const override { return 4; }
    };
    
    int main() {
        
        X1* x1 = new X4;
        std::cout << "x1->f(): " << x1->f() << std::endl;
        
        X4 x4;
        X1& x2 = x4;
        std::cout << "x2.f(): " << x2.f() << std::endl;
        
    }
    

     

    Line (1) uses virtual dispatch (late binding) via a pointer, and line (2) uses virtual dispatch via reference. Once more, here is the output with GCC 10.2 and the Compiler Explorer.

    virtualConstexpr

    The new Character Type for UTF-8 Strings: char8_t

    Additionally, to the character types char16_t and char32_t from C++11, C++20 gets the new character type char8_t.char8_t is large enough to represent any UTF-8 code unit (8 bits). It has the same size, signedness, and alignment as an unsigned char, but is a distinct type. 

    Consequently, C++20 has a new typedef for the character type char8_t (1) and a new UTF-8 string literal (2).

    std::u8string: std::basic_string<char8_t> (1)
    u8"Hello World"                           (2)
    

     

    The following program shows the straightforward usage of char8_t:

    // char8Str.cpp
    
    #include <iostream>
    #include <string>
    
    int main() {
    
        const char8_t* char8Str = u8"Hello world";
        std::basic_string<char8_t> char8String = u8"helloWorld";
        std::u8string char8String2 = u8"helloWorld";
        
        char8String2 += u8".";
        
        std::cout << "char8String.size(): " << char8String.size() << std::endl;
        std::cout << "char8String2.size(): " << char8String2.size() << std::endl;
        
        char8String2.replace(0, 5, u8"Hello ");
        
        std::cout << "char8String2.size(): " << char8String2.size() << std::endl;
     
    }
    

     

    Without further ado. Here is the output of the program on the Compiler Explorer.

     

    char8Str

    using enum in Local Scopes 

    A using enum declaration introduces the enumerators of the named enumeration in the local scope.

    // enumUsing.cpp
    
    #include <iostream>
    #include <string_view>
    
    enum class Color {
        red,
        green, 
        blue
    };
    
    std::string_view toString(Color col) {
      switch (col) {
        using enum Color;                   // (1) 
        case red:   return "red";           // (2) 
        case green: return "green";         // (2) 
        case blue:  return "blue";          // (2) 
      }
      return "unknown";
    }
    
    int main() {
    
        std::cout << std::endl;
       
        std::cout << "toString(Color::red): " << toString(Color::red) << std::endl;
        
        using enum Color;                                                    // (1)
        
        std::cout << "toString(green): " << toString(green) << std::endl;    // (2) 
        
        std::cout << std::endl;
    
    }  
    

     

    The using enum declaration in (1) introduces the enumerators of the scoped enumerations Color into the local scope. From then on, the enumerators can be used unscoped (2). This time, the only C++ compiler supporting using enum is the Microsoft Compiler 19.24:

    enumUsing

    Default member initializers for bit-fields

    First, what is a bit field? Here is the definition of Wikipedia: “A bit field is a data structure used in computer programming. It consists of a number of adjacent computer memory locations which have been allocated to hold a sequence of bits, stored so that any single bit or group of bits within the set can be addressed.[1][2] A bit field is most commonly used to represent integral types of known, fixed bit-width.”

    With C++20, we can default initialize the members of a bit-field:

     

    // bitField.cpp
    
    #include <iostream>
    
    struct Class11 {             // (1)
        int i = 1;
        int j = 2;
        int k = 3;
        int l = 4;
        int m = 5;
        int n = 6;
    };
    
    struct BitField20 {          // (2)
        int i : 3 = 1;
        int j : 4 = 2;
        int k : 5 = 3;
        int l : 6 = 4;
        int m : 7 = 5;
        int n : 7 = 6;
    };
    
    int main () {
        
        std::cout << std::endl;
    
        std::cout << "sizeof(Class11): " << sizeof(Class11) << std::endl;
        std::cout << "sizeof(BitField20): " << sizeof(BitField20) << std::endl;
        
        std::cout << std::endl;
        
    }
    

     

    According to the member of a class (1) with C++11, the members of the bit-field can have default initializers (2) with C++20. Finally, here is the output of the program with the Clang 10.0 compiler:

    bitField

    A Short Writing Break

    In the next fortnight, I will be in Italy, so I will not write a regular post.
    If you want to read one of my more than 300 posts on modern C++, I created a visual tour through my blog. This visual tour explains the TOC, categories, tags, archive, and search system and should help you find the post you are looking for.

    Here you go https://youtu.be/hrXoVSi0O28.

    What’s next?

    After my short break, I continue my journey through C++20 with the new library. In particular, I will write about std::span.

     

     

     

    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,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *