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).
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 guaranteed 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 recognize. 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
specializations.
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!