Breaking Down the Current State of C++ 17

shutterstock_95538487 (1)

Thirty-three years after C++ first appeared, the latest update to the C++ 17 standard is upon us, and there are two compilers that have implemented it.

Since C++ 11, a new release has been scheduled every three years. Having a regular schedule for releases helps with integrating features into the standard, as programming-language features tend to disappear when schedules are unspecified.

C++ 11 was a big update that took nine years to prepare, followed by C++ 14, which featured mostly minor corrections (other than generic lambdas). C++ 17 is a medium-sized update, with the number of pages in the standard now up to 1,572. It has nearly doubled in size since the 817-pages-long C++ 03.

State of Compiler Support

The degree of C++ 17 support varies between compilers such as Intel C++, MSVC++, Clang, and GCC. Take this cppreference webpage, for instance: only GCC currently has full support, with Clang almost there. I suspect the next versions of MS VC++ and Intel C++ will be more feature-complete.

Here are some of the features that are in the C++ 17 standard:

Functions Returning Multiple Values

C++, Go, Python, C# and Scala (amongst others) can return multiple values from a function; aside from Go, the rest do it by returning a tuple (an object that packs multiple values, possibly of different types). Like Go, C++ 17 can do this without a tuple; it’s called structured bindings. The syntax is this:

tuple<T1,T2,T3> f(/*...*/) { /*...*/ return {a,b,c}; }
auto [a,b,c] = f();

The function definition specifies a tuple, but the auto declaration allows it to create and initialize variables explicitly from the returned tuple’s components.

According to the proposal, this should also support the universal reference (auto &&), so range for is supported:

for( auto&& [first,second] : mymap ) { 
  // use first/second
}

On string_view

When you manipulate strings, there are often temporary copies—and these can incur a performance penalty. A string_view is a non-owning reference to a string, intended to be used as a temporary object for string manipulation with better performance. You can do many of the same operations on a string_view as a string, copy, substr, find, rfind, iterate through, and access using [], at, front, back and data.

Variants

Before variants, there were unions in C++. A union is a variable that can hold one of a number of different typed values. Consequently, it’s just big enough to hold the largest of the different types. All the variables occupy the same place in memory. In the Data type below, it can hold an int or a char:

     union Data {
       int i;  // 4 bytes
       char c; // 1 byte
     };

So an instance of Data would be 4 bytes long. If it holds an int, all four bytes are used. If it’s a char, then three bytes are wasted; it still occupies 4 bytes.

A variant (i.e., std::variant<type1,type2…>) is a type safe union but It can’t hold references, arrays or the type void. When you declare an instance of a variant variable, you specify the types that it can hold:

#include 

std::variant<int,float> s;
s = 17; 
int i = get(s);

Here, “s” can hold an int or a float, but is initialized to an int.

Directory and File Classes

These are based on the Boost Filesystem Library, which has been tried and tested for 13 years. This works on either POSIX systems or Windows, and lets C++ programs read or write files and directories, rename files, etc. It covers complexities such as / or \ for directory separator, symbolic or hard links, processing directories recursively, converting strings to match the underlying operating system charset (e.g. UTF16), etc. It also lets you treat POSIX sockets as files.

Guaranteed Copy Elision

Elision is a possible C++ compiler optimization that deals with temporary objects. For instance, you call a function that returns an object by value; that object is then used to initialize a variable, and usually involves two copies or moves.

Previously, the compiler was allowed to get rid of one of the extra copies/moves (Elision just means to omit), but it wasn’t guaranteed to happen. Now it is. The C++ 17 compiler must only do one copy or move.

Conclusions

There’s a lot of what I’d call minor changes, but collectively these alter the style of C++ programming. With switch and if statements, you can have an init statement as part of the body of the statement; it brings it in-scope, with slightly cleaner code.

if (auto it = m.find(10); it != m.end()) { return it->size(); }

Likewise, you can now modify execution policy to specify whether code runs sequentially or can be parallelized (std::par and optionally vectorized- std::par_vec):

int x = 0;
std::mutex m;
int a[] = {1,2};
std::for_each(std::par_vec, std::begin(a), std::end(a), [&](int) {
  std::lock_guard guard(m); // Error: lock_guard constructor calls m.lock()
  ++x;
});

To see a detailed list of all changes voted in after the November meetings, I recommend Michael Wong’s Issaquah PDF.

The only downside of the three-year release schedule is that C++ programmers will be playing catch-up to stay current. C++ is in an interesting place, with some arguing that it’s now a legacy language (others deny that, of course). The language certainly has a very large codebase, and can still be used to create operating systems, database servers and computer games. But will there be a lot of momentum on the part of the world’s programmers to update existing code to C++ 17?

Image Credit: Antonov Roman/Shutterstock.com

Post a Comment

Your email address will not be published.