This image provides a first impression of how C++26 is structured.
Advertisement
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.
fishing in dirty water
The image is only intended to give a first impression of what C++26 looks like. I will adjust it as needed over the next few months when I write about C++26. For example, three powerful features are reflection, contracts, and std::execution
took a big step towards their standardization. In contrast, I deliberately ignore pattern matching on the image.
Very effective features like reflection or contracts implement the agile idea minimum viable product But:
“A minimum viable product (MVP) is a version of a product that has enough features to be usable by early customers, who can provide feedback for future product development.”
This means that C++26 is only a starting point for features like reflection or contracts. If possible, I show implemented features. Many features are already implemented in brand new C++ compilers. For the rest, I’m hoping for prototype implementations.
I’ll start with the basic language.
original language
reflection
Reflection is the ability to examine, consider, and change the structure and behavior of a program. It makes compile-time programming in C++ much more powerful. I don’t want to bore you with too much theory in this article. That’s why I’ll show you my favorite example from the reflection proposal P2996R5,
A question I often have to answer in my courses is: How do I convert an enumerator to a string?
// enumString.cpp
#include
#include
#include
#include
// start 'expand' definition
namespace __impl {
template
struct replicator_type {
template
constexpr void operator>>(F body) const {
(body.template operator()(), ...);
}
};
template
replicator_type replicator = {};
}
template
consteval auto expand(R range) {
std::vector<:meta::info> args;
for (auto r : range) {
args.push_back(std::meta::reflect_value(r));
}
return substitute(^__impl::replicator, args);
}
// end 'expand' definition
template
requires std::is_enum_v // (1)
constexpr std::string enum_to_string(E value) {
std::string result = "";
(:expand(std::meta::enumerators_of(^E)):) >> // (2)
(&){
if (value == (:e:)) {
result = std::meta::identifier_of(e); // (3)
}
};
return result;
}
int main() {
std::cout << '\n';
enum Color { red, green, blue };
std::cout << "enum_to_string(Color::red): " << enum_to_string(Color::red) << '\n';
// std::cout << "enum_to_string(Color(42)): " << enum_to_string(42) << '\n';
std::cout << '\n';
}
In this example, the experimental features (std::meta
) is the standard used. Here is the output of the program:
I would like to talk briefly about function templates enum_to_string
Entering the ceremony expand
Just one solution. Function call enum_to_string(Color(42))
breaks because the function requires calculations: requires std::is_enum_v
(line 1).
Line (1) implements a reflection operator (^E)
and calls the meta function enum_to_std::meta::enumerators_of(^E)
But finally, the so-called Spicer ((:refl:)
, Grammatical elements for reflection in line (2). The second meta function on line (3) creates the string: std:meta::identifier_of(e))
.Metafunctions are executed at compile time, as is reflection.
Contract
A contract precisely and verifiably specifies the interfaces to software components. These software components are functions that satisfy preconditions, postconditions, and invariants.
Here is a simple example from the proposal P2900,
int f(const int x)
pre (x != 1) // a precondition assertion
post(r : r != 2) // a postcondition assertion; r refers to the return value of f
{
contract_assert (x != 3); // an assertion statement
return x;
}
Celebration f
There is a precondition, a postcondition, and an invariant. Preconditions are checked before the function call, postconditions after the function call, and invariants right at the time of the call.
Calling the function with arguments 1, 2, or 3 violates the contract. There are different ways to respond to a breach of contract.
void g()
{
f(0); // no contract violation
f(1); // violates precondition assertion of f
f(2); // violates postcondition assertion of f
f(3); // violates assertion statement within f
f(4); // no contract violation
}
There is a lot more to the core language of C++26 than just reflection and contracts. I would like to briefly mention them and present a code snippet from the respective proposals.
- Wildcards and extended character sets
auto (x, y, _) = f();
Underscore (_
) means “I don’t care.” and can be used multiple times.
static_assert
,Expansion
static_assert(sizeof(S) == 1,
std::format("Unexpected sizeof: expected 1, got {}", sizeof(S))
There are many improvements to templates in C++26. My favorite is packed indexing:
template
constexpr auto first_plus_last(T... values) -> T...(0) {
return T...(0)(values...(0) + values...(sizeof...(values)-1));
}
int main() {
//first_plus_last(); // ill formed
static_assert(first_plus_last(1, 2, 10) == 11);
}
delete("Should have a reason");
What will happen next?
In my next article I will provide an overview of the C++26 library.
(May)