volatile and other Small Improvements in C++20
This is a cross-post from www.ModernesCpp.com.
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.
volatile
The abstract in the proposal P1152R0 gives a short description of the changes which volatile undergo: "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:
- Deprecate volatile compound assignment, and pre/post increment/decrement
- Deprecate volatile qualification of function parameters or return types
- 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 so far: 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 for communication with a signal handler but not for communication with another thread of execution.
To make it short, volatile avoids aggressive optimization and has no multithreading semantic.
I present the remaining small 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 in line (3) a std::string. Additionally, in line (1) and line (2) I apply automatic type deduction for class templates which we have since C++17. Instead of std::vector<int> and std::initalizer_list<int>, I just write std::vector and std::initializer_list.
With GCC 10.2 and the Compiler Explorer, I get the expected output.
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. Either can a virtual constexpr function override a non-constexpr function, but also 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, line (2) virtual dispatch via reference. Once more, here is the output with GCC 10.2 and the Compiler Explorer.
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.
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 that point on, the enumerators can be used unscoped (2). This time, the only C++ compiler supporting using enum is the Microsoft Compiler 19.24:
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 bit-field can have default initializers (2) with C++20. Finally, here is the output of the program with the Clang 10.0 compiler:
A Short Writing Break
In the next fortnight, I will be in Italy and I will, therefore, not write a regular post.
In case you want to read in the meantime one of my more than 300 posts to modern C++; I created a visual tour through my blog. This visual tour explains the TOC, categories, tags, the archive, and the 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: Meeting C++, Matt Braun, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Marko, G Prvulovic, Reinhold Dr?ge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Wolfgang G?rtner, Jon Hess, Christian Wittenhorst, Louis St-Amour, Stephan Roslen, Venkat Nandam, Jose Francisco, Douglas Tinkham, Lakshman, Kuchlong Kuchlong, Avi Kohn, Serhy Pyton, Robert Blanch, Kuma [], Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, and Friedrich Huber.
Seminars
I'm happy to give online-seminars or face-to-face seminars world-wide. Please call me if you have any questions.
Standard Seminars
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ - The Core Language
- C++ - The Standard Library
- C++ - Compact
- C++11 and C++14
- Clean Code with Modern C++
- Concurrency with Modern C++
- Design Patterns and Architecture Patterns with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
Contact Me
- Tel.: +49 7472 917441
- Mobil: +49 152 31965939
- Mail: [email protected]
- German Seminar Page: www.ModernesCpp.de
- English Seminar Page: www.ModernesCpp.net
Whenever you work with unicode strings (compare/search/replace) you need to make sure that both strings are in normal form. You need to add a comment that the string both strings before replace are in normal form otherwise code in general case is not correct.