C++23: The Small Pearls in the Core Language
The C++23 core language has more to offer than deducing this. Today, I will write about the small pearls.
Literal Suffixes
C++23 provides new integral literal suffixes for (signed) std::size_t
:
Literal Suffix | Type | Example |
z or Z | signed std::size_t | auto signed = -2023z; |
z/Z and u/U | std::size_t | auto unsigned = 2023uz, |
Let me start with a simple example to see the value of the new suffixes.
Assume you want to iterate through a vector. For optimization reasons, you cache the vectors’ size.
#include <vector> int main() { std::vector<int> v{0, 1, 2, 3}; for (auto i = 0, s = v.size(); i < s; ++i) { /* use both i and v[i] */ } }
When you compile the code, you get the following error message in the Compiler Explorer:
The reason is that auto
deduced i
to int
and s
to long unsigned int
. Consequentially, making both variables unsigned will also not fix the issue.
#include <vector> int main() { std::vector<int> v{0, 1, 2, 3}; for (auto i = 0u, s = v.size(); i < s; ++i) { /* use both i and v[i] */ } }
Now, the compiler deduces i
to unsigned int
but s
still to long unsigned int
. The following screenshot shows the Compiler Explorers error output once more.
Thanks to C++23, the new literal suffix z
will fix this issue.
#include <vector> int main() { std::vector<int> v{0, 1, 2, 3}; for (auto i = 0uz, s = v.size(); i < s; ++i) { /* use both i and v[i] */ } }
This example is based on the proposal P0330R8. The proposal has more motivating examples for the new literal suffixes.
if consteval
if consteva
l behaves like if (std::is_constant_evaluated()) { }
but has a few advantages:
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
- No header
<type_traits>
is required. - Has a more straightforward syntax like
std::is_constant_evaluated
. - Can be used to invoke an immediate function.
Let me add a few words. std::is_constant_evaluated
is a C++20 feature that detects if a constexpr
function is evaluated during compile time.
cppreference.com/is_constant_evaluated has an excellent example:
#include <cmath> #include <iostream> #include <type_traits> constexpr double power(double b, int x) { if (std::is_constant_evaluated() && !(b == 0.0 && x < 0)) { // A constant-evaluation context: Use a constexpr-friendly algorithm. if (x == 0) return 1.0; double r {1.0}; double p {x > 0 ? b : 1.0 / b}; for (auto u = unsigned(x > 0 ? x : -x); u != 0; u /= 2) { if (u & 1) r *= p; p *= p; } return r; } else { // Let the code generator figure it out. return std::pow(b, double(x)); } } int main() { // A constant-expression context constexpr double kilo = power(10.0, 3); int n = 3; // Not a constant expression, because n cannot be converted to an rvalue // in a constant-expression context // Equivalent to std::pow(10.0, double(n)) double mucho = power(10.0, n); std::cout << kilo << " " << mucho << "\n"; // (3) }
The function power
is constexpr
. This means it has the potential to run at compile time. The first call of power
causes a compile-time execution because the result is requested at compile-time: constexpr double kilo = power(10.0, 1)
. On the contrary, the second call can only be performed at run time because the function argument n
is no constant expression: double mucho = power(10.0, n
).
Thanks to std::is_constant_evaluated
, different code is performed at compile time and run time. At compile time, it’s if
branch is performed, and at run time, it’s else
branch. Both power
calls return 1000.
An immediate function is a consteval
function. A consteval
function is a function that can only run at compile time. You can read more about consteval
functions in my C++20 post: Two new Keywords in C++20: consteval and constinit.
Based on consteval if
, you can implement std::is_constant_evaluated
:
constexpr bool is_constant_evaluated() { if consteval { return true; } else { return false; } }
auto(x) and auto{x}
A generic way to obtain a copy of an object in C++ is auto copy = x;
. This is fine but has one issue: copy
is an lvalue, but you sometimes want a prvalue. prvalue is short for pure rvalue. A pure rvalue is an expression whose evaluation initializes an object. Read Barry’s post: Value Categories in C++17, to learn more about value categories.
The calls auto(x)
and auto{x}
cast x
into a prvalue as if passing x
as a function argument by value. auto(x)
and auto{x}
perform a decay copy. What?
I often have the question in my class about what decay means. Therefore, let me elaborate on this. Decay essentially means that some type information is lost when you copy a value. A typical example is a function taking its argument by value. Here are the flavors of decay:
- Array-to-Pointer conversion
- Function-to-Pointer conversion
- Discarding const/volatile qualifiers
- Removing references
The following program shows the four flavors of decay:
// decay.cpp void decay(int*, void(*)(int), int, int ) { } // (5) void func(int){} // (2) int main() { int intArray[5]{1, 2, 3, 4, 5}; // (1) const int myInt{5}; // (3) const int& myIntRef = myInt; // (4) decay(intArray, func, myInt, myIntRef); }
The function decay
(5) requires a pointer to an int
, a function pointer, and two int
s. The first argument of the function call is an int
array (1), the second a function (2), the third a const int
(3), and the last is a const int&
(4).
The type-traits library has the function std::decay
. This function enables you to apply these decay conversions directly on a type. Accordingly, these are the corresponding decay conversions using std::decay
.
// decayType.cpp #include <type_traits> int main() { // (1) // int[5] -> int* static_assert(std::is_same<std::decay<int[5]>::type, int*>::value); // (2) // void(int) -> void(*)(int) static_assert(std::is_same<std::decay<void(int)>::type, void(*)(int)>::value); // (3) // const int -> int static_assert(std::is_same<std::decay<const int>::type, int>::value); // (4) // const int& -> int static_assert(std::is_same<std::decay<const int&>::type, int>::value); }
What’s next?
I’m not done with the small pearls in C++23. In my next post, I will continue my journey with C++23 core language features.
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)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!