DevelopKit

New features in C++17 for LHCb Physicists

Overview

Teaching: 20 min
Exercises: 0 min
Questions
  • What can I use from the improvements C++ in 2017?

Objectives
  • Learn a few of the most useful tools in 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 areas that particle physicists can use.

Parallel standard library algorithms

You can now set an execution policy on algorithms in the standard library; sequential, parallel, and vector 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

Remember, if you are already in a multithreaded application such as Gaudi, you may not get much benefit from multithreading further.

Also, the Gaudi framework will use a Threaded Building Blocks (TBB) backend to run the components multithreaded, which means that individual components should not be also multithreaded with a different scheme. If these eventually play together more nicely, this warning may go away.

Filesystem

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.

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.

Syntax improvements

Constructor argument deduction

Templated constructors (finally!) support template deduction, allowing them to behave like functions. This makes the old function wrappers, like make_tuple and make_shared 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> return_two_args() {
    return make_tuple(1.0, 2);
}
double a, int b;
std::tie(a,b) = return_two_args();
// C++17 syntax
auto return_two_args() {
    return std::tuple(1.0, 2);
}
auto [a, b] = return_two_args();

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 a static if constexpr statement. 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 cases; you should already be assuming copy elision in C++11/14 since the compilers have had the option to do it since C++11. 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.

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 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 while the standard is being finalized. Also see Herb Sutter’s site for reports on the progress made at the C++ standards meetings.

Key Points