# C++17 ## Overview Many of these descriptions and examples are taken from various resources (see [Acknowledgements](#acknowledgements) section) and summarized in my own words. C++17 includes the following new language features: - [template argument deduction for class templates](#template-argument-deduction-for-class-templates) - [declaring non-type template parameters with auto](#declaring-non-type-template-parameters-with-auto) - [folding expressions](#folding-expressions) - [new rules for auto deduction from braced-init-list](#new-rules-for-auto-deduction-from-braced-init-list) - [constexpr lambda](#constexpr-lambda) - [lambda capture this by value](#lambda-capture-this-by-value) - [inline variables](#inline-variables) - [nested namespaces](#nested-namespaces) - [structured bindings](#structured-bindings) - [selection statements with initializer](#selection-statements-with-initializer) - [constexpr if](#constexpr-if) - [utf-8 character literals](#utf-8-character-literals) - [direct-list-initialization of enums](#direct-list-initialization-of-enums) - [\[\[fallthrough\]\], \[\[nodiscard\]\], \[\[maybe_unused\]\] attributes](#fallthrough-nodiscard-maybe_unused-attributes) - [\_\_has\_include](#\_\_has\_include) - [class template argument deduction](#class-template-argument-deduction) C++17 includes the following new library features: - [std::variant](#stdvariant) - [std::optional](#stdoptional) - [std::any](#stdany) - [std::string_view](#stdstring_view) - [std::invoke](#stdinvoke) - [std::apply](#stdapply) - [std::filesystem](#stdfilesystem) - [std::byte](#stdbyte) - [splicing for maps and sets](#splicing-for-maps-and-sets) - [parallel algorithms](#parallel-algorithms) - [std::sample](#stdsample) - [std::clamp](#stdclamp) - [std::reduce](#stdreduce) - [prefix sum algorithms](#prefix-sum-algorithms) - [gcd and lcm](#gcd-and-lcm) - [std::not_fn](#stdnot_fn) - [string conversion to/from numbers](#string-conversion-tofrom-numbers) ## C++17 Language Features ### Template argument deduction for class templates Automatic template argument deduction much like how it's done for functions, but now including class constructors. ```c++ template struct MyContainer { T val; MyContainer() : val{} {} MyContainer(T val) : val{val} {} // ... }; MyContainer c1 {1}; // OK MyContainer MyContainer c2; // OK MyContainer ``` ### Declaring non-type template parameters with auto Following the deduction rules of `auto`, while respecting the non-type template parameter list of allowable types[\*], template arguments can be deduced from the types of its arguments: ```c++ template struct my_integer_sequence { // Implementation here ... }; // Explicitly pass type `int` as template argument. auto seq = std::integer_sequence(); // Type is deduced to be `int`. auto seq2 = my_integer_sequence<0, 1, 2>(); ``` \* - For example, you cannot use a `double` as a template parameter type, which also makes this an invalid deduction using `auto`. ### Folding expressions A fold expression performs a fold of a template parameter pack over a binary operator. * An expression of the form `(... op e)` or `(e op ...)`, where `op` is a fold-operator and `e` is an unexpanded parameter pack, are called _unary folds_. * An expression of the form `(e1 op ... op e2)`, where `op` are fold-operators, is called a _binary fold_. Either `e1` or `e2` is an unexpanded parameter pack, but not both. ```c++ template bool logicalAnd(Args... args) { // Binary folding. return (true && ... && args); } bool b = true; bool& b2 = b; logicalAnd(b, b2, true); // == true ``` ```c++ template auto sum(Args... args) { // Unary folding. return (... + args); } sum(1.0, 2.0f, 3); // == 6.0 ``` ### New rules for auto deduction from braced-init-list Changes to `auto` deduction when used with the uniform initialization syntax. Previously, `auto x {3};` deduces a `std::initializer_list`, which now deduces to `int`. ```c++ auto x1 {1, 2, 3}; // error: not a single element auto x2 = {1, 2, 3}; // x2 is std::initializer_list auto x3 {3}; // x3 is int auto x4 {3.0}; // x4 is double ``` ### constexpr lambda Compile-time lambdas using `constexpr`. ```c++ auto identity = [](int n) constexpr { return n; }; static_assert(identity(123) == 123); ``` ```c++ constexpr auto add = [](int x, int y) { auto L = [=] { return x; }; auto R = [=] { return y; }; return [=] { return L() + R(); }; }; static_assert(add(1, 2)() == 3); ``` ```c++ constexpr int addOne(int n) { return [n] { return n + 1; }(); } static_assert(addOne(1) == 2); ``` ### Lambda capture `this` by value Capturing `this` in a lambda's environment was previously reference-only. An example of where this is problematic is asynchronous code using callbacks that require an object to be available, potentially past its lifetime. `*this` (C++17) will now make a copy of the current object, while `this` (C++11) continues to capture by reference. ```c++ struct MyObj { int value {123}; auto getValueCopy() { return [*this] { return value; }; } auto getValueRef() { return [this] { return value; }; } }; MyObj mo; auto valueCopy = mo.getValueCopy(); auto valueRef = mo.getValueRef(); mo.value = 321; valueCopy(); // 123 valueRef(); // 321 ``` ### Inline variables The inline specifier can be applied to variables as well as to functions. A variable declared inline has the same semantics as a function declared inline. ```c++ // Disassembly example using compiler explorer. struct S { int x; }; inline S x1 = S{321}; // mov esi, dword ptr [x1] // x1: .long 321 S x2 = S{123}; // mov eax, dword ptr [.L_ZZ4mainE2x2] // mov dword ptr [rbp - 8], eax // .L_ZZ4mainE2x2: .long 123 ``` It can also be used to declare and define a static member variable, such that it does not need to be initialized in the source file. ```c++ struct S { S() : id{count++} {} ~S() { count--; } int id; static inline int count{0}; // declare and initialize count to 0 within the class }; ``` ### Nested namespaces Using the namespace resolution operator to create nested namespace definitions. ```c++ namespace A { namespace B { namespace C { int i; } } } ``` The code above can be written like this: ```c++ namespace A::B::C { int i; } ``` ### Structured bindings A proposal for de-structuring initialization, that would allow writing `auto [ x, y, z ] = expr;` where the type of `expr` was a tuple-like object, whose elements would be bound to the variables `x`, `y`, and `z` (which this construct declares). _Tuple-like objects_ include [`std::tuple`](README.md#tuples), `std::pair`, [`std::array`](README.md#stdarray), and aggregate structures. ```c++ using Coordinate = std::pair; Coordinate origin() { return Coordinate{0, 0}; } const auto [ x, y ] = origin(); x; // == 0 y; // == 0 ``` ```c++ std::unordered_map mapping { {"a", 1}, {"b", 2}, {"c", 3} }; // Destructure by reference. for (const auto& [key, value] : mapping) { // Do something with key and value } ``` ### Selection statements with initializer New versions of the `if` and `switch` statements which simplify common code patterns and help users keep scopes tight. ```c++ { std::lock_guard lk(mx); if (v.empty()) v.push_back(val); } // vs. if (std::lock_guard lk(mx); v.empty()) { v.push_back(val); } ``` ```c++ Foo gadget(args); switch (auto s = gadget.status()) { case OK: gadget.zip(); break; case Bad: throw BadFoo(s.message()); } // vs. switch (Foo gadget(args); auto s = gadget.status()) { case OK: gadget.zip(); break; case Bad: throw BadFoo(s.message()); } ``` ### constexpr if Write code that is instantiated depending on a compile-time condition. ```c++ template constexpr bool isIntegral() { if constexpr (std::is_integral::value) { return true; } else { return false; } } static_assert(isIntegral() == true); static_assert(isIntegral() == true); static_assert(isIntegral() == false); struct S {}; static_assert(isIntegral() == false); ``` ### UTF-8 character literals A character literal that begins with `u8` is a character literal of type `char`. The value of a UTF-8 character literal is equal to its ISO 10646 code point value. ```c++ char x = u8'x'; ``` ### Direct list initialization of enums Enums can now be initialized using braced syntax. ```c++ enum byte : unsigned char {}; byte b {0}; // OK byte c {-1}; // ERROR byte d = byte{1}; // OK byte e = byte{256}; // ERROR ``` ### \[\[fallthrough\]\], \[\[nodiscard\]\], \[\[maybe_unused\]\] attributes C++17 introduces three new attributes: `[[fallthrough]]`, `[[nodiscard]]` and `[[maybe_unused]]`. * `[[fallthrough]]` indicates to the compiler that falling through in a switch statement is intended behavior. This attribute may only be used in a switch statement, and must be placed before the next case/default label. ```c++ switch (n) { case 1: // ... [[fallthrough]]; case 2: // ... break; case 3: // ... [[fallthrough]]; default: // ... } ``` * `[[nodiscard]]` issues a warning when either a function or class has this attribute and its return value is discarded. ```c++ [[nodiscard]] bool do_something() { return is_success; // true for success, false for failure } do_something(); // warning: ignoring return value of 'bool do_something()', // declared with attribute 'nodiscard' ``` ```c++ // Only issues a warning when `error_info` is returned by value. struct [[nodiscard]] error_info { // ... }; error_info do_something() { error_info ei; // ... return ei; } do_something(); // warning: ignoring returned value of type 'error_info', // declared with attribute 'nodiscard' ``` * `[[maybe_unused]]` indicates to the compiler that a variable or parameter might be unused and is intended. ```c++ void my_callback(std::string msg, [[maybe_unused]] bool error) { // Don't care if `msg` is an error message, just log it. log(msg); } ``` ### \_\_has\_include `__has_include (operand)` operator may be used in `#if` and `#elif` expressions to check whether a header or source file (`operand`) is available for inclusion or not. One use case of this would be using two libraries that work the same way, using the backup/experimental one if the preferred one is not found on the system. ```c++ #ifdef __has_include # if __has_include() # include # define have_optional 1 # elif __has_include() # include # define have_optional 1 # define experimental_optional # else # define have_optional 0 # endif #endif ``` It can also be used to include headers existing under different names or locations on various platforms, without knowing which platform the program is running on, OpenGL headers are a good example for this which are located in `OpenGL\` directory on macOS and `GL\` on other platforms. ```c++ #ifdef __has_include # if __has_include() # include # include # elif __has_include() # include # include # else # error No suitable OpenGL headers found. # endif #endif ``` ### Class template argument deduction *Class template argument deduction* (CTAD) allows the compiler to deduce template arguments from constructor arguments. ```c++ std::vector v{ 1, 2, 3 }; // deduces std::vector std::mutex mtx; auto lck = std::lock_guard{ mtx }; // deduces to std::lock_guard auto p = new std::pair{ 1.0, 2.0 }; // deduces to std::pair ``` For user-defined types, *deduction guides* can be used to guide the compiler how to deduce template arguments if applicable: ```c++ template struct container { container(T t) {} template container(Iter beg, Iter end); }; // deduction guide template