Tuesday, July 30

C++20 Is Feature Complete; Here’s What Changes Are Coming

If you have an opinion about C++, chances are you either love it for its extensiveness and versatility, or you hate it for its bloated complexity and would rather stick to alternative languages on both sides of the spectrum. Either way, here’s your chance to form a new opinion about the language. The C++ standard committee has recently gathered to work on finalizing the language standard’s newest revision, C++20, deciding on all the new features that will come to C++’s next major release.

After C++17, this will be the sixth revision of the C++ standard, and the language has come a long way from its “being a superset of C” times. Frankly, when it comes to loving or hating the language, I haven’t fully made up my own mind about it yet. My biggest issue with it is that “programming in C++” can just mean so many different things nowadays, from a trivial “C with classes” style to writing code that will make Perl look like prose. C++ has become such a feature-rich and downright overwhelming language over all these years, and with all the additions coming with C++20, things won’t get easier. Although, they also won’t get harder. Well, at least not necessarily. I guess? Well, it’s complex, but that’s simply the nature of the language.

Anyway, the list of new features is long, combining all the specification proposals is even longer, and each and every one of these additions could fill its own, full-blown article. But to get a rough idea about what’s going to come to C++ next year, let’s have a condensed look at some of these major new features, changes, and additions that will await us in C++20. From better type checking and compiler errors messages to Python-like string handling and plans to replace the #include system, there’s a lot at play here!

Making Things Safer

As a language, being more liberal and less restrictive on implementation details provides great flexibility for developers — along with a lot of potential for misunderstandings that are bound to result in bugs somewhere further down the road. It is to this day the biggest asset and weakness of C, and C++ still has enough similarities in its roots to follow along with it. Restrictions can surely help here, but adding restrictions tends to be an unpopular option. The good thing is, C++ has compromises in place that leave the flexibility on the language level, and adds the restrictions at the developer’s own discretion.

Compiler Advisory: Explicit Constants

Back in C++11, the constexpr keyword was introduced as an addition to a regular const declaration, defining a constant expression that can be evaluated at compile time. This opens up plenty of optimization opportunities for the compiler, but also makes it possible to declare that, for example, a function will return a constant value. That helps to more clearly show a function’s intent, avoiding some potential headaches in the future. Take the following example:

int foo() {
    return 123;
}

constexpr int bar() {
    return 123;
}

const int first = foo();
const int second = bar();

While there is technically no difference between these two functions, and either one will return a constant value that will be valid to assign to a const variable, bar() will make this fact explicitly clear. In the case of foo(), it’s really more of a coincidental side effect, and without full context, it is not obvious that the function’s return value is supposed to be a constant. Using constexpr eliminates any doubt here and avoids possible accidental side effects, which will make the code more stable in the long run.

Having already been in place for a while, constexpr has seen a few improvements over the years, and will see some more with C++20, especially in terms of removing previously existing limitations on their usage. Most the new standard allows virtual constexpr functions, developers can use try / catch inside constexpr (provided no exceptions are thrown from within), and it’s possible to change members inside of a union.

On top of that, both std::string and std::vector as well as a bunch of other previously missing places in the standard library will fully utilize constexpr. Oh, and if you want to check if a piece of code is actually executed within a constant evaluation, you will be able to do so using std::is_constant_evaluated() which returns a boolean value accordingly.

Note that constexpr code states that it can be evaluated at compile time and is therefore a valid constant expression, but it doesn’t necessarily have to, nor is it guaranteed that the evaluation will happen at compile time, but could be postponed to run time. This is mainly relevant for compiler optimization though and doesn’t affect the program’s behavior, but also shows that constexpr is primarily an intention marker.

constexpr int foo(int factor) {
    return 123 * factor;
}

const int const_factor = 10;
int non_const_factor = 20;

const int first = foo(const_factor);
const int second = foo(non_const_factor);

Here, first will be evaluated at compile time as all expressions and values involved are constants and as such known at compile time, while second will be evaluated at run time since non_const_factor itself is not a constant. It doesn’t change the fact though that foo() is still going to return a constant value, the compiler just can’t be sure yet which exact value that may be. To make sure the compiler will know the value, C++20 introduces the consteval keyword to declare a function as an immediate function. Declaring foo() as consteval instead of constexpr will now indeed cause an error. In fact, immediate functions are really only known at compile time, and as a consequence this turns the consteval functions into an alternative for macro functions.

At the other end of the constant expression verification strictness is the new constinit keyword that is mainly telling the compiler that an object will be statically initialized with a constant value. If you are familiar with the static initialization order fiasco, this is an attempt to solve the issue.

But constant expressions aren’t the only C++20 changes aimed at improving compile time validation, and the stability that comes with it.

The Concept Of Concepts

While technically not a completely new thing, Concepts have graduated from being an experimental feature to a full-fledged part of the language standard, allowing the addition of semantic constraints to templates, and ultimately making generic programming a hint more specific.

Somewhat related to type traits, Concepts make sure that data used within a template fulfill a specified set of criteria, and verifies this at the beginning of the compilation process. So as an example, instead of checking that an object is_integral, an object of type Integral is used. As a result, the compiler can provide a short and meaningful error message if the defined requirement of a concept isn’t met, instead of dumping walls of errors and warnings from somewhere deep within the template code itself that won’t make much sense without digging further into that code.

Apart from letting the compiler know what data is needed, it also shows rather clearly to other developers what data is expected, helping to avoid error messages in the first place, and avoids misunderstandings that lead to bugs later on. Going the other direction, Concepts can also be used to constrain the return type of template functions, limiting variables to a Concept rather than a generic auto type, which can be considered at C++’s void * return type.

Some basic Concepts will be provided in the standard library, and if you don’t want to wait for updated compilers, GCC has the experimental Concepts implemented since version 6 and you can enable them with the -fconcepts command line parameter. Note that in the initial draft and current reference documentation, Concept names were defined using CamelCase, but they will be changed to snake_case to preserve consistency with all other standard identifiers.

Ranges Are The New Iterators

Ranges are essentially iterators that cover a sequence of values in collections such as lists or vectors, but instead of constantly dragging the beginning and end of the iterator around, ranges just keep them around internally.

Just as Concepts, Ranges have also moved from experimental state to the language standard in C++20, which isn’t much of a coincidence as Ranges depend on Concepts and uses them to improve the old iterator handling by making it possible to add constraints to the handled values, with the same benefits. On top of constraining value types, Ranges introduce Views as a special form of a range, which allows data manipulation or filtering on a range, returning a modified version of the initial range’s data as yet another range. This allows them to be chained together. Say you have a vector of integers and you want to retrieve all even values in their squared form — ranges and views can get you there.

With all of these changes, the compiler will be of a lot more assistance for type checking and will present more useful error messages.

String Formatting

Speaking of error messages, or well, output in general, following the proposal of its author the libfmt library will be integrated into the language standard as std::format. Essentially this provides Python’s string formatting functionality! Compared to the whole clumsiness of the cout shifting business, and the fact that using printf() in the context of C++ just feeling somewhat wrong, this is definitely a welcomed addition.

While the Python style formatting offers pretty much the same functionality as printf(), just in a different format string syntax, it eliminates a few redundancies and offers some useful additions, such as binary integer representation, and centered output with or without fill characters. However, the biggest advantage is the possibility to define formatting rules for custom types, on the surface this is like Python’s __str__() or Java’s toString() methods, but it also adds custom formatting types along the way.

Take strftime() as example — albeit it this is a C function, which behaves as snprintf(), the difference is that it defines custom, time-specific conversion characters for its format string, and expects struct tm as argument. With the right implementation, std::format could be extended to behave just like that, which is in fact what the upcoming addition to the std::chrono library is going to do.

Source Location

While we’re on the subject of nicely formatting in convenient ways, another experimental feature coming to C++20 is the source_location functionality, providing convenient access to the file name, line number, or function name from the current call context. In combination with std::format, a prime candidate for implementing a custom logging function, and practically a modern alternative to preprocessor macros like __FILE__ and __LINE__.

Modules

It appears that slowly eliminating use of the preprocessor is a long-term goal in the future of C++, with consteval essentially replacing macro functions, source_location obsoleting one of the most commonly used macros, and on top of all that: modules, a new way to split up source code that aims to eventually replace the whole #include system.

While some say it’s long overdue, others see the addition of modules at this point rather critical, and some developers have stated their concerns about the current state. Whatever your own opinion is on the subject, it’s safe that say that this is a major change to the whole essence of the language, but at the same time a complex enough undertaking that won’t just happen over night. Time will tell where modules will actually end up. If you’re curious and want to have a look at it already, both GCC and Clang already have module support to some extent.

But wait, there is more!

Everything Else

The list just goes on, with Coroutines as one more major feature that will be added to C++20.

As for all the rest, there will be

Don’t worry though, the parts that really matter to be volatile won’t change.

So all in all, a lot is coming to C++, and some features are sure worthy to be excited about.

Of course, some of these new features and extensions have been around in other languages for ages, if not even from the beginning. It’s interesting to see though how some of these languages that were once influenced by C++ are now influencing the very future of C++ itself.

No comments:

Post a Comment