What's in C++17?

JF Bastien <@jfbastien>

Slides borrow heavily from Bryce Adelstein Lelbach <@blelbach>

github.com/jfbastien/what-is-cpp17

The Standards Process

isocpp.org/std

When is it coming?

  1. Ballot resolution in February
  2. Officially adopted in July

(If everything goes well)

📑 current draft

When can I use it?

See the cpprefence table, or the individual status for:

What's in it?

Language

  • Structured bindings
  • if constexpr
  • constexpr lambdas
  • Inline variables
  • Folding expressions
  • template <auto>
  • Constructor template argument deduction
  • Guaranteed copy elision
  • noexcept added to the type system
  • if (init; cond) and switch (init; cond)
  • namespace A::B::C {}
  • Single-parameter static_assert()
  • __has_include()

Library

  • <filesystem>
  • <memory_resource>
  • string_view
  • optional<>
  • variant<>
  • any
  • Parallel algorithms
  • Aligned new
  • shared_mutex and variadic lock_guard<>
  • Math special functions
  • invoke(), apply(), and is_callable<>
  • *_v<> variable templates for type traits
  • conjunction<>, disjunction<>, and negation<>

🐞 Bug fixes 🐞


New bugs? 🐜

Structured binding

Do you do this often?


void g(T t) {
  auto x = t.x;
  auto y = t.y;
  auto z = t.z;
  // ...
}

How about this?


void g(T& t) {
  auto& x = t.x;
  auto& y = t.y;
  auto& z = t.z;
  // ...
}

Ain't this beautiful?


std::tuple<T1, T2, T3> f();

T1 x; T2 y; T3 z;
std::tie(x, y, z) = f();

Oops-prone...


std::tuple<T1, T2, T3> f();

int x; T2 y; T3 z;
std::tie(y, y, z) = f();

What about:

  • Non-default-constructible types
  • Types we don't want to initialize (expensive?)
  • const
  • Types with reference members

std::array<T, 3> f();

T x, y, z;
std::tie(x, y, z) = f(); // INVALID.

😕


struct S { T1 x; T2 y; T3 z; };

S f();

T1 x; T2 y; T3 z;
std::tie(x, y, z) = f(); // INVALID.

🤔


auto [x, y, z] = obj;

😁

The type of obj must be Destructurable:

  • Either all non-static data members:
    • Must be public
    • Must be direct members of the type or members of the same public base class of the type
    • Cannot be anonymous unions
  • Or the type has:
    • An obj.get<>() method or an ADL-able get<>(obj) overload
    • Specializations of std::tuple_size<> and std::tuple_element<>

Why won't the compiler just figure it out for me?


std::tuple<int, double> t(-42, 3.14);
return std::tuple<int, double>(-42, 3.14);

auto t = std::make_tuple(-42, 3.14);
return std::make_tuple(-42, 3.14);

Any why make_* all the things?

How does it work?

  • Simple declarations of variables (or variable templates) that are also definitions whose declarator is a noptr-declarator (i.e. not when declaring functions, template parameters, function parameters, non-static data members, pointers, references etc.), and
  • Explicit type conversion (functional notation)

if constexpr

Template code is often specialized for the zero- and one-parameter case.


void g() { /* Zero parameters. */ } 

template<typename T>
void g(T&& t) { /* Handle one T. */ } 

template<typename T, typename... Rest>
void g(T&& t, Rest&&... r) {
  g(std::forward<>(t));        // One T.
  g(std::forward<Rest>(r)...); // And then the Rest, recursively.
}

if constexpr (cond1)
  statement1;
else if constexpr (cond2)
  statement2;
else
  statement3;

Consider this


template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... a);

template<typename T, typename... Args> 
std::enable_if_t<
  std::is_constructible_v<T, Args...>, std::unique_ptr<T>
> 
make_unique(Args&&... a) {
  return std::unique_ptr(new T(std::forward<Args>(a)...));
}  

template<typename T, typename... Args>  
std::enable_if_t<
  !std::is_constructible_v<T, Args...>, std::unique_ptr<T>
>
make_unique(Args&&... a) {
  return std::unique_ptr(new T{std::forward<Args>(a)...});
}

😵

Folding expressions

Similar to if constexpr, template code often repeats what to do, or is expressed in terms of recursion.


auto sum() { return 0; }

template <typename T>
auto sum(T&& t) { return t; }

template <typename T, typename... Rest>
auto sum(T&& t, Rest&&... r) {
  return t + sum(std::forward<Rest>(r)...);
}

🌀 Will it fold? 🌀

Unary Right(E op ...)≣E1 op (... op (EN-1 op EN))
Unary Left(... op E)≣((E1 op E2) op ...) op EN
Binary Right(E op ... op I)≣E1 op (... op (EN-1 op (EN op I)))
Binary Left(I op ... op E)≣(((I op E1) op E2) op ...) op EN

All binary operators are foldable


        ==  !=  <   >   <=  >=  &&  ||  ,   .*  ->*  = 
        +   -   *   /   %   ^   &   |   <<   >> 
        +=  -=  *=  /=  %=  ^=  &=  |=  <<=  >>=

A gentleperson-programmer is someone who knows this fact but chooses not to exercise it. Especially when combined with operator overloading.

Unary fold expressions

If the parameter pack is empty then the value of the fold is:

&&≣true
||≣false
, ≣void()

For any operator not listed above, an unary fold expression with an empty parameter pack is ill-formed.

template <auto>


template <typename T, T v>
struct integral_constant {
  static constexpr T value = v;
};
void f() { }

auto w = integral_constant<int, 2048>;
auto x = integral_constant<char, 'a'>;
auto y = integral_constant<bool, true>;
auto z = integral_constant<decltype(f), f>;

🙄 so much repetition


template <typename T, T... Elements>
struct integer_sequence { };

auto seq0 = integer_sequence<std::size_t, 0, 1, 2>;
auto seq1 = integer_sequence<char, 'h', 'i'>;

🙄 so much repetition

if (init; cond)

and switch (init; cond)

<filesystem>

namespace fs = std::filesystem;
  • Based on boost.filesystem
  • Somewhat POSIX
  • ToCToU races included 😫 (wat dat?)
  • Non-member functions taking paths
    • fs::path
    • fs::directory_entry
    • fs::directory_iterator
    • fs::file_status

Exception versus error-code: why not both? 🤷

  • void copy(fs::path const& from, fs::path& to);
  • void copy(fs::path const& from, fs::path& to, std::error_code& ec);

std::string_view

A better const char*

  • Similar to std::string API
  • Non-owning pointer + length
  • No allocation
  • Value semantics
  • Can't modify contents

std::optional<>

  • Nullable object wrapper
  • No allocation
  • Value semantics
  • Pointer-like API

std::variant<>

  • Discriminate union
  • No allocation
  • Value semantics
  • Visitor-style access

Parallel Algorithms

Many STL algorithms, with parallel / vector


std::for_each(std::par, first, last,
    [](auto& x){ process(x); }
);
std::copy(std::par, first, last);
std::sort(std::par, first, last);
std::transform(std::par_unseq, xfirst, xlast, yfirst,
    [=](double xi, double yi){ return a * xi + yi; }
);
adjacent_difference is_heap_until replace_copy_if
adjacent_find is_partitioned replace_if
all_of is_sorted reverse
any_of is_sorted_until reverse_copy
copy lexicographical_compare rotate
copy_if max_element rotate_copy
copy_n merge search
count min_element search_n
count_if minmax_element set_difference
equal mismatch set_intersection
fill move set_symmetric_difference
fill_n none_of set_union
find nth_element sort
find_end partial_sort stable_partition
find_first_of partial_sort_copy stable_sort
find_if partition swap_ranges
find_if_not partition_copy transform
generate remove uninitialized_copy
generate_n remove_copy uninitialized_copy_n
includes remove_copy_if uninitialized_fill
inner_product remove_if uninitialized_fill_n
inplace_merge replace unique
is_heap replace_copy unique_copy

Execution policies

std::seqindeterminately sequenced in the calling thread
std::parindeterminately sequenced with respect to each other within the same thread
std::par_unsequnsequenced with respect to each other and possibly interleaved

See Hartmut Kaiser's CppCon talk!(slides)

He dives deep into C++ parallel algorithms, their performance, and where he thinks they'll go. Especially cool: parallel algorithms combined with auto-lambda, SIMD, executors, and coroutines.

mind == 💥💥💥

What's in it?

Here's what we covered

Language

  • ☑ Structured bindings
  • ☑ if constexpr
  • ☐ constexpr lambdas
  • ☐ Inline variables
  • ☑ Folding expressions
  • ☑ template <auto>
  • ☑ Constructor template argument deduction
  • ☐ Guaranteed copy elision
  • ☐ noexcept added to the type system
  • ☑ if (init; cond) and switch (init; cond)
  • ☐ namespace A::B::C {}
  • ☐ Single-parameter static_assert()
  • ☐ __has_include()

Library

  • ☑ <filesystem>
  • ☐ <memory_resource>
  • ☑ string_view
  • ☑ optional<>
  • ☑ variant<>
  • ☐ any
  • ☑ Parallel algorithms
  • ☐ Aligned new
  • ☐ shared_mutex and variadic lock_guard<>
  • ☐ Math special functions
  • ☐ invoke(), apply(), and is_callable<>
  • ☐ *_v<> variable templates for type traits
  • ☐ conjunction<>, disjunction<>, and negation<>

More we didn't talk about...

Language

  • C99 base → C11 base
  • Specified expression evaluation order
  • Forward progress guarantees
  • Attributes on namespaces and enums
  • [[fallthrough]], [[nodiscard]], and [[maybe_unused]]
  • UTF-8 character
  • Hexadecimal floating point literals
  • Generalized range-based for
  • Lambda capture of *this by value
  • auto a{b}; will not initialize initializer_list<>
  • Removed trigraphs, operator++(bool), and register

Library

  • destroy_*() and new uninitialized_*() utilities
  • Splicing and improved insertion for associative containers
  • .is_always_lockfree()
  • hardware_*_interference_size
  • uncaught_exceptions()
  • Improved conversions in unique_ptr<>
  • owner_less<:void>
  • noexcept cleanup for allocators and containers
  • ContiguousIterator concept
  • as_const(), clamp(), sample(), search(), and searchers
  • non-const string::data()
  • Incomplete type support for vector<> and lists
  • Non-member size(), empty(), and data()
  • void_t<> and bool_constant<>
  • floor(), ceil(), round(), and abs() for <chrono> types
  • Removed auto_ptr<> and random_shuffle()
  • Removed C++98 function objects and binders

🎊 C++17 🎉


Now compiling in toolchains near you!


These slides:

github.com/jfbastien/what-is-cpp17