C++17

The every-three-year cycle has changed the development of C++; we are now getting consistent releases somewhere in-between the major and minor releases of old. The 2017 release may be called minor by some, with a huge portion of the planned improvements being pushed back another 3-6 years, but there were several substantial changes in useful areas; it is much more impactful than C++14, for example. This almost feels like a lead-in release to C++20.

The std::variant, std::optional, and std::any additions to the standard library are huge, and can restructure the way you program (and are available for older C++ releases through Boost and other libraries).


Posts in the C++ series: 11 14 17 20

Syntax improvements

GCC 7 should be feature complete for syntax improvements (as is Clang 5).

Constructor argument deduction

Templated constructors (finally!) support template deduction, allowing them to behave like functions. This makes the old function wrappers, like make_tuple, obsolete. This makes many templated classes much easier to use for the average user.

// C++11
std:::tuple<int, double> my_tuple(my_int, my_double);
// C++17
std::tuple my_tuple(my_int, my_double);

Tuple syntax

Tuple syntax is now part of the language, rather than just part of the library. Combined with the previous improvement to constructors, this allows the following to be written in Python-like syntax:

C++11 syntax

std::tuple<double,int> two_returns() {
    return std::make_tuple(1.0, 2);
}
double a, int b;
std::tie(a,b) = two_returns();

C++17 syntax

auto two_returns() {
    return std::tuple(1.0, 2);
}
auto [a, b] = two_returns();

Along with the C++14 auto return type deduction, C++17 allows the tuple to be created directly without a wrapper function or template parameters, and it allows the variables to be created and assigned directly in the assignment statement (structured bindings). This also works for std::arrays and some structs, and can be overloaded for custom classes. You can also do this (or any one line initializer) inside an if statement followed by a semicolon (like for), and it will be valid until the end of the else clause. A couple of functions have been added to make using tuples as arguments easier; std::apply for functions and std::make_from_tuple for classes.

Reducing usage of the preprocessor

The slow removal of the secondary preprocessor language continues with the amazing if constexpr statement. This kills families of template metaprogramming workarounds by ignoring the unevaluated branch at compile time. So you can check to see if a type supports something, then do that something in the if constexpr statement; if it does not support it, it will still compile.

Lambdas now can be constexpr, and are allowed in constexpr functions. And while we are on the subject of lambda’s, they also gained the ability to capture the pointer to the current class by value, with [*this].

The language is now better defined, with less left up to the compiler implementation. The order of expression evaluation is no longer left up to the compiler in most cases, reducing subtle portability bugs.

The compiler is now guarantied to avoid a copy (copy elision) in many simple cases; you can even return a class that does not support moving or copying; the ownership is directly transferred.

Inline variables are finally allowed, removing the compiler errors that required irritating workarounds and separate .cpp files when all you needed was a header file. You still are not supposed to have global variables, but if you do, now they are easier to write correctly.

Variadic macros now have folding syntax, which allows you to perform reduction with them easily. Inside your template function with variadic parameter args you can do 0 + ... + args to sum over args (other operators are supported, including ,. The ... syntax can be used for using declarations now, too.

#if __has_include(<headerfile>) now allows you use the preprocessor to check for availability of header files, greatly simplifying the build system, since you can now directly check to see if a library is available.

New attributes have been added, such as [[maybe_unused]]. Attributes are allowed in more places, and there is now a clear requirement that a compiler ignore any attribute that it does not recognise. These hopefully will finally begin to replace the various #pragmas currently in use.

Smaller features

You can nest namespaces directly now.

namespace A::B {...

Template arguments can be auto, which is any non-type argument. Hexadecimal float point literals are allowed. List initialization works for enums. C11 is now the base, instead of C99.

Standard Template Library

Utilities

The removal of using raw pointers and unsafe C syntax continues with three tremendously useful classes from Boost. Feel free to check the cppreference page or Boost libraries for examples and usage.

Optional

One of the common uses for pointers is the creation of a value that might not exist (nullptr). This has the undesirable side effect of dynamically allocating memory, and requires the optional deletion of the allocated memory. std::optional provides a safe, stack based solution that clearly defines your intent, as well.

std::optional<Massive> maybe = maybe_make_massive_obj();
if(maybe) {
    std::cout << "Massive object exists: " << *maybe;
}

Variant

std::variant provides a C++ syntax for a safe C union replacement. It can store a value from a predefined list of types. The size of the object is equal to the largest possible contained type.

std::variant<int, std::string> either;
either = 2; // Now an int
either = "hi"; // Now a string
try {
    int x = std::get<int>(either); 
} catch (std::bad_variant_access&) {
    std::cout << "This is not an int";
}

One particularly powerful feature is the .visit method, which takes a callable that must be valid for all possible types. You can combine this with auto lambdas to process the contents very generally.

Any

std::any should help kill the desire to use unsafe void pointer casts. It can hold anything, and allows access with any_cast specialisations.

String view

A new class, std::string_view, replaces the usage of std::string&, and promises faster string usage. Libraries should use it, and the user will not notice a difference except faster code. (If you use ROOT, you’ve probably already seen errors mentioning string_view when you didn’t match C++ versions correctly; this comes from ROOT trying to backport string_view.)

Parallel standard library algorithms

Not widely available yet! You can use the Intel Parallel STL, however, which was merged into GCC 9 and is in progress for Clang.

You can now set an execution policy on algorithms in the standard library; sequential (seq), parallel (par), and vectorized parallel (par_unseq) are allowed. It is integrated into the algorithms library; you just add the execution policy as the first argument to the algorithm. For example:

std::vector<double> vals = {1.0, 2.0, 3.0, 4.0};
auto my_square = [](double value){return value*value;}
std::for_each(std::parallel::par_vec, vals.begin(), vals.end(), my_square);

Most of the old algorithms support these execution policies, and several new algorithms have been added to cover parallel usage.

Note that a very useful fourth policy; unseq, for vectorized but not threaded execution, was added to the Intel Parallel STL, and so is available most places the PSTL is available. This became an official part of the standard in C++20, as well.

Filesystem

This was added in libstdc++ that comes with GCC 8, and no longer requires a special flag to use in GCC 9. Libc++ has it as well, but still requires a special linking flag.

The powerful Boost file system has finally made it into the standard library, after three revisions and promises every three years. This allows object oriented syntax for file manipulation, and works on Windows/Mac/Linux. However, it probably will not be integrated into libraries very quickly, requiring casting to C-style strings to be used. Should still be very useful. If you are familiar with Python’s pathlib or a similar library, this will seem familiar.

A simple example of the new filesystem:

auto file_in_path = std::filesystem::current_path() / "file.txt";
file_in_path.replace_extension(".log");
if(std::filesystem::exists(file_in_path))
    ...

Here we see that the / operator joins path segments, the paths have useful member functions, and the non-member functions can operate on the underlying filesystem. There exists a full set of filesystem operations. Generally, any documentation you find for Boost::filesystem Version 3 should also be applicable.

Assorted standard library additions

There are a few smaller additions to the standard library too, like the addition of special math functions, such as Bessel functions. If you have ever needed to clamp a value between two limits, std::clamp(value, min, max) avoids nested min/max calls or duplication of the value. std::lcm and std::gcd have been added as mathematical functions, as well.

A few new helper functions make dealing with generalized classes easier, including std::invoke, which can call any callable, std::not_fn which can negate any callable, and tuple’s apply, which applies a tuple as arguments to a callable.

Non-member versions of common tasks, std::size, std::empty, and std::data have been added to join the std::begin and std::end family.

There is now a class, std::byte, designed to model binary data in a more natural way than using char.

There are also a set of modifications to existing features, listed in the official document that are too small to list here.

Further reading

There are other changes that are either too small to discuss here, or are not likely to affect the average particle physicist. Like C++14, many of them polish the newer portions of the language. The official overview is excellent. A very good overview can be found on StackOverflow.

Also see Herb Sutter’s site for reports on the progress made at the C++ standards meetings for information about C++20 and beyond!

comments powered by Disqus