# C++11 and Beyond!
11
14
17
20
[Dvir Yitzchaki](https://github.com/dvirtz) Note: - The first C++ standard was published on 1998 with a small bug fix update on 2003. - After being stale for over a decade, starting from 2011, the standard switched to a train model where a new standard is being released every 3 years. - In this series we're going to discuss the new language and library features added to the C++ standard in the new revisions released since. - On each session we will have one main topic and smaller features will be introduced as needed. - Let's start. --- ## Chapters 1. [`auto`](#/1) 2. [Move Semantics](#/2) 3. [List Initialization](#/3) 4. [Variadic Templates](#/4) 5. [Lambda Expressions](#/5) 6. [Smart Pointers](#/6) 7. [`chrono`](#/7) 8. [Error Handling](#/8) 9. [Concurrency](#/9) 10. [Regular Expressions](#/10) 11. [Containers](#/11) 12. [Random](#/12) 13. [`constexpr`](#/13) 14. [C++17 Vocabulary Types](#/14) 16. [`filesystem`](#/15) --- ## Index
- [C++11](#/cpp11) - [C++14](#/cpp14) - [C++17](#/cpp17) - [C++20](#/cpp20)
--- ## C++11
### Language - [x] [`auto`](#/auto) and [`decltype`](#/decltype) - [x] [defaulted](#/defaulted) and [deleted](#/deleted) functions - [x] [`final`](#/final) and [`override`](#/override) - [x] [trailing return type](#/trailing_return) - [x] [rvalue references](#/rvalue_references) - [x] [move constructors](#/move_constructors) and [move assignment operators](#/move_constructors) - [x] [scoped enums](#/scoped_enums) - [x] [`constexpr`](#/constexpr) and [literal types](#/literal_type) - [x] [list initialization](#/list_initialization) - [x] [delegating](#/delegating_constructors) and inherited constructors - [ ] brace-or-equal initializers - [x] [`nullptr`](#/nullptr) - [ ] `long long` - [x] [`char16_t` and `char32_t`](#/utf_chars) - [x] [type aliases](#type_aliases) - [x] [variadic templates](#/variadic_templates) - [ ] generalized (non-trivial) unions - [ ] generalized PODs (trivial types and standard-layout types) - [x] [Unicode string literals](#/utf_chars) - [x] [user-defined literals](#/UDL) - [x] [attributes](#/attributes) - [x] [lambda expressions](#/lambda_expressions) - [x] [`noexcept` specifier](#/noexcept_specifier) and [`noexcept` operator](#/noexcept_operator) - [ ] `alignof` and `alignas` - [x] multithreaded [memory model](#/atomic) - [x] [thread-local](#/thread_local) storage - [ ] GC interface - [x] [range based for loop](#/range_for) - [x] [`static_assert`](#/static_assert) ### Library - [x] [atomic](#/atomic) operations library - [x] [`emplace()`](#/emplace) - [x] [`std::unique_ptr`](#/unique_ptr), [`std::shared_ptr`](#/shared_ptr), [`std::weak_ptr`](#/weak_ptr) - [ ] `std::move_iterator` - [x] [`std::initializer_list`](#/initializer_list) - [ ] stateful and scoped allocators - [x] [`std::forward_list`](#/forward_list) - [x] [`chrono`](#/chrono) library - [x] [`ratio`](#/ratio) library - [ ] new algorithms - [ ] Unicode conversion facets - [x] [`thread`](#/concurrency) library - [x] [`std::exception_ptr`](#/exception_ptr) - [x] [`std::error_code`](#/error_code) and [`std::error_condition`](#/error_condition) - [x] iterator improvements - [x] [`std::begin`/`end`](#/begin_end) - [x] [`std::next`/`prev`](#/next_prev) - [ ] Unicode conversion functions - [x] [`std::array`](#/array) - [x] [`std::tuple`](#/tuple)
--- ## C++14
### Language - [x] variable templates(#/variable_templates) - [x] [generic lambdas](#/generic_lambdas) - [x] [lambda init-capture](#/lambda_init_capture) - [ ] `new`/`delete` elision - [x] [relaxed](#/relaxed_constexpr) restrictions on `constexpr` functions - [x] [binary literals](#/binary_literals) - [x] [digit separators](#/binary_literals) - [x] [return type deduction](#/auto_return) for functions - [ ] aggregate classes with default non-static member initializers. ### Library - [x] [`std::make_unique`](#/make_unique) - [x] `std::shared_timed_mutex` and [`std::shared_lock`](#/shared_lock) - [ ] `std::integer_sequence` - [ ] `std::exchange` - [ ] `std::quoted` - [ ] two-range overloads for some algorithms - [ ] type alias versions of type traits - [x] user-defined literals for [`basic_string`](#/string_literals), [`duration`](#/duration_literals) and [`complex`](#/complex_literals)
--- ## C++17
### Language - [x] [fold-expressions](#/fold_expressions) - [x] [class template argument deduction](#/ctad) - [ ] non-type template parameters declared with auto - [x] compile-time [`if constexpr`](#/constexpr_if) - [ ] inline variables - [ ] structured bindings - [ ] initializers for if and switch - [ ] u8 character literal - [ ] simplified nested namespaces - [ ] using-declaration declaring multiple names - [ ] made `noexcept` part of type system - [ ] new order of evaluation rules - [ ] guaranteed copy elision - [ ] lambda capture of `*this` - [x] [`constexpr` lambda](#/constexpr_lambda) - [ ] attribute namespaces don't have to repeat - [ ] new attributes: - [ ] `[[fallthrough]]` - [ ] `[[nodiscard]]` - [ ] `[[maybe_unused]]` - [ ] __has_include
--- ## C++17
### Library - [ ] `tuple`: - [ ] `apply` - [ ] deduction guides - [ ] `make_from_tuple` - [x] [`variant`](#/variant) - [ ] `launder` - [ ] `to_chars`/`from_chars` - [ ] `as_const` - [ ] searchers - [x] [`optional`](#/optional) - [x] [`any`](#/any) - [ ] `not_fn` - [ ] uninitialized memory - [ ] `destroy_at` - [ ] `destroy` - [ ] `destroy_n` - [ ] `uninitialized_move` - [ ] `uninitialized_value_construct` - [ ] `weak_from_this` - [ ] polymorphic allocators - [ ] `aligned_alloc` - [ ] transparent `owner_less` - [ ] array support for `shared_ptr` - [ ] allocation functions with explicit alignment - [ ] `byte` - [ ] [`conjunction`](#/conjunction)/`disjunction`/`negation` - [x] type trait [variable templates](#/variable_templates) (xxx_v) - [ ] `is_swappable` - [ ] `is_invocable` - [ ] `is_aggregate` - [ ] `has_unique_object_representations` - [ ] `clamp` - [ ] execution policies - [ ] `reduce` - [ ] `inclusive_scan` - [ ] `exclusive_scan` - [ ] `map`/`set` extract and merge - [ ] `map`/`unordered_map` `try_emplace` and `insert_or_assign` - [ ] contiguous iterators - [ ] non-member `size`/`empty`/`data` - [ ] mathematical special functions - [ ] `gcd` - [ ] `lcm` - [ ] 3D `hypot` - [x] [`is_always_lock_free`](#/is_always_lock_free) - [x] variadic [`lock_guard`](#/scoped_lock) - [ ] cache line interface - [ ] `uncaught_exceptions` - [ ] `timespec_get` - [x] [rounding functions](#/chrono_rounding) for `duration` and `time_point`
--- ## C++20
### Language - [ ] Feature test macros - [ ] 3-way comparison `operator<=>` and `operator==() = default` - [ ] designated initializers - [ ] init-statements and initializers in range-for - [x] [char8_t](#/utf_chars) - [ ] new attributes - [ ] `[[no_unique_address]]` - [ ] `[[likely]]` - [ ] `[[unlikely]]` - [ ] pack-expansions in lambda captures - [ ] removed the requirement to use `typename` to disambiguate types in many contexts - [x] [`consteval`](#/consteval), [`constinit`](#/constinit) - [x] further relaxed [`constexpr`](#/20_constexpr) - [ ] signed integers are 2's complement - [ ] aggregate initialization using parentheses - [ ] Coroutines - [ ] Modules - [ ] Constraints and concepts - [ ] Abbreviated function templates - [ ] DR: array new can deduce array size
--- ## C++20
### Library - [ ] Formatting library - [ ] Calendar and Time Zone library - [ ] `std::source_location` - [ ] `std::span` - [ ] `std::endian` - [ ] array support for `std::make_shared` - [ ] `std::remove_cvref` - [ ] `std::to_address` - [ ] floating point and `shared_ptr` atomics - [ ] `std::barrier`, `std::latch`, and `std::counting_semaphore` - [ ] `std::jthread` and thread cancellation classes - [ ] `
` - [ ] `std::osyncstream` - [ ] `std::u8string` and other `char8_t` uses - [x] [`constexpr` for `
`, `
`, `
`](#/20_constexpr_lib/0) - [ ] `std::string::starts_with` / `ends_with` and `std::string_view::starts_with` / `ends_with` - [ ] `std::assume_aligned` - [ ] `std::bind_front` - [ ] `std::c8rtomb`/`std::mbrtoc8` - [ ] `std::make_obj_using_allocator` etc - [ ] `std::make_shared_for_overwrite` / `std::make_unique_for_overwrite` - [ ] heterogeneous lookup in unordered associative containers - [ ] `std::polymoprhic_allocator` with additional member functions and `std::byte` as its default template argument - [ ] `std::execution::unseq` - [ ] `std::midpoint` and `std::lerp` - [ ] `std::ssize` - [ ] `std::is_bounded_array`, `std::is_unbounded_array` - [ ] Ranges - [ ] uniform container erasure (`std::erase`/`std::erase_if`) - [ ] Mathematical constants
---- --- ## What problems can you find in this code? ```cpp ///fails=conversion from 'int' to non-scalar type 'std::vector
' requested ///hide #include
#include
void foo() { ///unhide // (a) int i; // (b) std::vector
v; int size = v.size(); // (c) std::vector
v1(5); std::vector
v2 = 5; ///hide } ``` Note: - uninitialized variable - possible data lose converting from `size_t` to `int` - although looking similar, the second line does not compile, because the constructor is *explicit* --- ## And this? ```cpp ///fails=conversion from '__normal_iterator
' to non-scalar type '__normal_iterator
' requested ///hide #include
///unhide void traverser( const std::vector
& v ) { for( std::vector
::iterator i = v.begin(); i != v.end(); ++i ) { // ... } } ``` Note: - this code does not compile because we need a `const_iterator` --- ## And this? ```cpp ///hide #include
struct gadget{ gadget() { std::cout << "gadget()\n"; } gadget(const gadget&) { std::cout << "gadget(gadget&)\n"; } }; struct widget{ widget() { std::cout << "widget()\n"; } widget(const gadget&) { std::cout << "widget(gadget&)\n"; } }; ///unhide gadget get_gadget(); ///hide gadget get_gadget() { return gadget(); } int main() { ///unhide widget w = get_gadget(); ///hide } ```
assuming `gadget` is implicitly convertible to `widget`
Note: - a temporary `getget` is created which might be a performance pitfall, as the creation of the temporary object is not at all obvious from reading the call site alone. - it's possible that using `gadget` is just as well as viable in this code --- ## Non member `begin` and `end` Accept anything with a member `begin` and `end` as well as C-style arrays ```cpp ///hide #include
#include
int main() { ///unhide std::vector
v = { 3, 1, 4 }; auto vi = std::begin(v); assert(*vi == 3); int a[] = { -5, 10, 15 }; auto ai = std::begin(a); assert(*ai == -5); ///hide } ``` Note: - prefer to use this in generic code --- What does this function do? ```cpp ///hide #include
#include
using namespace std; ///unhide template
void some_function( Container& c, const Value& v ) { if( find(begin(c), end(c), v) == end(c) ) c.push_back(v); assert( !c.empty() ); } ``` Note: - `append_unique` --- # auto
Slides are based on [Herb Sutter's](https://herbsutter.com) GOTW series
Note: - our main topic today - oldest feature in C++11 (first implementation at 1983) --- ## declaring a local variable syntax ```cpp ///hide void foo(int its_initial_value){ ///unhide auto my_new_variable = its_initial_value; ///hide } ``` deduce type from initializing expression ```cpp ///hide void foo(int its_initial_value){ ///unhide auto x = 0x12345678ULL; // type of x is unsigned long long ///hide } ``` --- ## similar to template type deduction ```cpp template
void f( T ) { } ///hide int main() { ///unhide int val = 0; f( val ); // deduces T == int, calls f
( val ) auto x = val; // deduces T == int, x is of type int ///hide } ``` --- ## Strips off qualifiers and references ```cpp ///hide int main() { ///unhide int val = 0; int& ir = val; auto e = ir; // The type of e is int const int ci = val; auto h = ci; // The type of h is int const int* cip = &val; auto i = cip; // The type of i is const int* int* const ipc = &val; auto j = ipc; // The type of j is int* ///hide } ``` Note: You want your new variable to be just like some existing variable or expression over there, and be initialized from it, but that only means that you want the same basic type, not necessarily that other variable’s own personal secondary attributes such as top-level const– or volatile-ness and &/&& reference-ness which are per-variable. For example, just because he’s const doesn’t mean you’re const, and vice versa. --- ## Can be qualified ```cpp ///hide int main() { ///unhide int val = 0; auto a = val; // The type of a is int auto& b = val; // The type of b is int& const auto c = val; // The type of c is const int const auto& d = val; // The type of d is const int& ///hide } ``` Note: If needed, `const` and `&` can be explicitly added --- --- ## Forces initialization instead of ```cpp ///hide int main() { ///unhide int i; ///hide } ``` write ```cpp ///hide int main() { ///unhide auto i = 42; // guaranteed to be initialized ///hide } ``` Note: solving the first problem --- ## Avoid narrowing conversions instead of ```cpp ///hide #include
#include
int main() { ///unhide std::vector
v; int size = v.size(); ///hide } ``` write ```cpp ///hide #include
#include
int main() { std::vector
v; ///unhide auto size = v.size(); // exact type, no narrowing ///hide } ``` Note: solving narrowing conversions --- ## DRY initialization syntax instead of ```cpp ///hide #include
int main() { ///unhide std::vector
v2 = std::vector
(5); ///hide } ``` write ```cpp ///hide #include
int main() { ///unhide auto v2 = std::vector
(5); // keep it DRY ///hide } ``` Note: if one wants to use explicit constructor with the assignment syntax they would have to repeat the type but not with auto --- ## Correct type by default instead of ```cpp ///hide #include
void foo(std::vector
&v) { ///unhide std::vector
::const_iterator i = v.begin(); ///hide } ``` write ```cpp ///hide #include
void foo(std::vector
&v) { ///unhide auto i = begin(v); ///hide } ``` Note: - correct and clear and simpler - stays correct if we change the type of the parameter to be non-const - or even replace vector with some other type of container --- ## Avoids hidden temporaries instead of ```cpp ///hide #include
struct gadget{ gadget() { std::cout << "gadget()\n"; } gadget(const gadget&) { std::cout << "gadget(gadget&)\n"; } }; struct widget{ widget() { std::cout << "widget()\n"; } widget(const gadget&) { std::cout << "widget(gadget&)\n"; } }; gadget get_gadget() { return gadget(); } int main() { ///unhide widget w = get_gadget(); ///hide } ``` write ```cpp ///hide #include
struct gadget{ gadget() { std::cout << "gadget()\n"; } gadget(const gadget&) { std::cout << "gadget(gadget&)\n"; } }; struct widget{ widget() { std::cout << "widget()\n"; } widget(const gadget&) { std::cout << "widget(gadget&)\n"; } }; gadget get_gadget() { return gadget(); } int main() { ///unhide auto w = get_gadget(); // gadget can be used ///hide } ``` or ```cpp ///hide #include
struct gadget{ gadget() { std::cout << "gadget()\n"; } gadget(const gadget&) { std::cout << "gadget(gadget&)\n"; } }; struct widget{ widget() { std::cout << "widget()\n"; } widget(const gadget&) { std::cout << "widget(gadget&)\n"; } }; gadget get_gadget() { return gadget(); } int main() { ///unhide auto w = widget(get_gadget()); // widget is needed ///hide } ``` --- ## `static_assert` Check assertions at compile time: ```cpp ///hide constexpr auto bool_constexpr = true; #define message "should never appear" ///unhide static_assert ( bool_constexpr , message ); ``` For example: ```cpp static_assert(sizeof(void *) == 8, "Only 64-bit code generation is supported."); ``` Note: - If you only support 64 bit you can give a compile time error if anyone tries to use you code to compile for other architectures - Open CE --- ## `decltype` Similar to `sizeof(expr)` but returns type instead of size. ```cpp #include
int f(); static_assert(sizeof(f()) == sizeof(int), "f should return int"); static_assert(std::is_same
::value, "f should return int"); ``` --- ## what should be the return type ```cpp ///hide #include
#define RETURN_TYPE auto ///unhide template
RETURN_TYPE trace(Func f, T t) { std::cout << "Calling f on " << t; return f(t); } ``` Note: we implement a generic tracing function which gets a function `f` and a value `t` and returns the result of `f(t)`. --- ## what should be the return type first try ```cpp ///fails='t' was not declared in this scope ///hide #include
///unhide template
decltype(f(t)) trace(Func f, T t) { std::cout << "Calling f on " << t; return f(t); } ``` Note: - open CE - to express the return type we need to refer to `f` and `t` which are unknown to the compiler at the point of defining the return type. --- ## trailing return type ```cpp ///hide #include
///unhide template
auto trace(Func f, T t) -> decltype(f(t)) { std::cout << "Calling f on " << t; return f(t); } ``` Note: we replace the return type with `auto` and add the actual return type after the arrow. At this point we can refer to `f` and `t`. --- ## `auto` return type (C++14) ```cpp ///hide #include
///unhide template
auto trace(Func f, T t) { std::cout << "Calling f on " << t; return f(t); } ``` Note: in c++14, this idiom was shortened to mean deduce the return type from the return statement --- # CONCERNED? Note: - There are number of popular concerns about using auto. - Let's tackle some of them. --- > writing auto to declare a variable is primarily about saving typing. --- Writing auto is about - [x] correctness - [x] performance - [x] maintainability - [x] robustness - [x] convenience Note: No, writing auto is about - correctness - performance - maintainability - robustness - AND FINALLY, ALSO - convenience Next... --- > But in some cases I do want to commit to a specific type, not automatically deduce it, so I can’t use auto. --- Use ```cpp ///hide template
void foo(type init) { ///unhide auto x = type(init); ///hide } ``` Note: WRONG! --- > My code gets unreadable quickly when I don’t know what exact type my variable is without hunting around to see what that function or expression returns, so I can’t just use auto all the time. --- How many concrete types are in this function? ```cpp ///hide #include
#include
using namespace std; ///unhide template
void append_unique( Container& c, const Value& v ) { if( find(begin(c), end(c), v) == end(c) ) c.push_back(v); assert( !c.empty() ); } ``` Note: the lack of exact types makes it much more powerful and doesn’t significantly harm its readability --- ## write code against interfaces, not implementations - Functions - hiding code - OO - hiding code and data - Polymorphism - hiding type Note: - we write functions to hide implementation code - we write class to hide private members and methods - we use static (templates) and dynamic (virtual methods) polymorphism to write generic code - using `auto` is another link in this software development chain --- ## Using `auto` - guarantees the variable will be initialized - efficient by default - guarantees that you will use the correct exact type - guarantees that you will continue to use the correct exact type - is the only good option for hard-to-spell and impossible-to-spell types - is just generally simpler and less typing --- # Thank you ---- --- ## How many copies? ```cpp ///hide #include
#include
///unhide template
std::vector
generateBuffers(const size_t INSTANCES, const size_t BUFFER_SIZE) { std::vector
v; for (size_t i = 0; i < INSTANCES; ++i) { v.push_back(Buffer(BUFFER_SIZE)); } return v; } ``` Note: - copying from temporary buffer - copying when capacity is full - possible copying when returning `v` - in all theses cases the source is destroyed immediately after copying --- ## Member initialization ```cpp [|4-6|6] ///hide #include
struct HashingFunction{ HashingFunction(const std::string&); }; struct D {}; int f(D); ///unhide class A { public: A() : a(7), b(5), hash_algorithm("MD5"), s("Constructor run") {} A(int a_val) : a(a_val), b(5), hash_algorithm("MD5"), s("Constructor run") {} A(D d) : a(f(d)), b(a), hash_algorithm("MD5"), s("Constroctor run") {} private: int a, b; HashingFunction hash_algorithm; std::string s; }; ``` --- ## Non-static data member initializers ```cpp [|8-10|4-6] ///hide #include
struct HashingFunction{ HashingFunction(const std::string&); }; struct D {}; int f(D); ///unhide class A { public: A() {} A(int a_val) : a(a_val) {} A(D d) : a(f(d)), b(a) {} private: int a = 7, b = 5; HashingFunction hash_algorithm = HashingFunction("MD5"); std::string s = "Constructor run"; }; ``` Note: `auto` is not allowed even with initializer; --- ## Delegating constructors ```cpp [|4-9|10] ///hide #include
struct HashingFunction{ HashingFunction(const std::string&); }; struct D {}; int f(D); ///unhide class A { public: A(int _a = 7, int _b = 5, const HashingFunction& _hash_algorithm = HashingFunction("MD5"), const std::string& _s = "Constructor run") : a(_a), b(_b), hash_algorithm(_hash_algorithm), s(_s) {} A(D d) : A(f(d), a) {} private: int a, b; HashingFunction hash_algorithm; std::string s; }; ``` --- ## `nullptr` A type safe replacement for `NULL` macro ```cpp ///external ///compiler=vcpp_v19_24_x64 ///options=/O2 ///hide #include
///unhide void foo(int); void foo(char*); ///hide void call(){ ///unhide foo(42); // calls foo(int); foo(NULL); // calls foo(int); foo(nullptr); // calls foo(char*); ///hide } ``` --- ## Buffer copy ```cpp [|2-3|5-10|12-18|20-29|31] ///hide #include
///unhide struct Buffer { size_t m_size; int* m_pArray; Buffer(size_t size = 0) : m_size(size) , m_pArray(size == 0 ? nullptr : new int[size]) {} Buffer(const Buffer& other) : Buffer(other.m_size) { std::copy(other.m_pArray, other.m_pArray + m_size, m_pArray); } Buffer& operator=(const Buffer& other) { delete[] m_pArray; m_size = other.m_size; m_pArray = new int[m_size]; std::copy(other.m_pArray, other.m_pArray + m_size, m_pArray); return *this; } ~Buffer() { delete[] m_pArray; } }; ``` --- ## implement assignment by copy constructor ```cpp [20-26] ///hide #include
///unhide struct Buffer { size_t m_size; int* m_pArray; Buffer(size_t size = 0) : m_size(size) , m_pArray(size == 0 ? nullptr : new int[size]) {} Buffer(const Buffer& other) : Buffer(other.m_size) { std::copy(other.m_pArray, other.m_pArray + m_size, m_pArray); } Buffer& operator=(const Buffer& other) { Buffer tmp(other); std::swap(m_size, tmp.m_size); std::swap(m_pArray, tmp.m_pArray); return *this; } ~Buffer() { delete[] m_pArray; } }; ``` --- ## Steal from temporary ```cpp ///hide #include
struct Buffer { size_t m_size; int* m_pArray; Buffer(size_t size = 0) : m_size(size) , m_pArray(size == 0 ? nullptr : new int[size]) {} ///unhide Buffer(const Buffer& temporary) : m_size(temporary.m_size) , m_pArray(temporary.m_pArray) {} ///hide ~Buffer() { delete[] m_pArray; } }; ``` Note: - As we know the temporary won’t be used after the assignment operator, why not “move” its resources to the new object instead of copy them? - There's a problem with this code though - double delete --- ## Without double delete ```cpp ///hide #include
struct Buffer { size_t m_size; int* m_pArray; Buffer(size_t size = 0) : m_size(size) , m_pArray(size == 0 ? nullptr : new int[size]) {} ///unhide Buffer(Buffer& temporary) : m_size(temporary.m_size) , m_pArray(temporary.m_pArray) { temporary.m_size = 0; temporary.m_pArray = nullptr; } ///hide ~Buffer() { delete[] m_pArray; } }; ``` --- # Move semantics --- ## How can we tell? ```cpp ///hide #include
struct Buffer { size_t m_size; int* m_pArray; Buffer(size_t size = 0) : m_size(size) , m_pArray(size == 0 ? nullptr : new int[size]) {} Buffer(const Buffer& other) : Buffer(other.m_size) { std::copy(other.m_pArray, other.m_pArray + m_size, m_pArray); } ~Buffer() { delete[] m_pArray; } }; int main() { const auto BUFFER_SIZE = 4; ///unhide Buffer c = Buffer(BUFFER_SIZE); // can move Buffer d = c; // cannot move // keep using c and d ///hide } ``` Note: We need a way to distinguish between references to temporary objects and regular objects. --- ## Value type Originally: > - l-value: can be on the left hand side of an assignment > - r-value: can be **only** on the right hand side of an assignment --- ## Left or right? ```cpp [|4|5|6|7|8|9] ///hide #include
#include
using std::min; int main() { ///unhide int a, b, *p; std::vector
v(2); a = 42; b = a; b = a * b; p = new int; *p = min(a, b); v.front() = 6; ///hide } ``` --- ## Value type More accurately: > an expression is an *l*-value if it has a specific memory location and its address can be taken using the & operator, otherwise it is an *r*-value. --- ## Value type ```cpp ///fails=lvalue required as unary '&' operand ///hide void foo() { ///unhide int a, b; int* c; c = &a; c = &(a * b); // error ///hide } ``` All expressions returning temporary values are r-values. All named objects are l-values. --- ## L-value references A (non-const) reference can be bound only to l-values: ```cpp ///fails=cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int' int a, b; int& c = a; int& d = (a * b); // error ``` --- ## R-value references Can be bound only to r-values and uses the && syntax. ```cpp ///fails=cannot bind rvalue reference of type 'int&&' to lvalue of type 'int' int a = 1, b = 2; int&& c = a; // error int&& d = (a * b); cout << ++d << endl; ``` Note: `d` is extending the lifetime of `(a * b)` --- ## Move constructor and assignment operator ```cpp [|3-11,22-24|13-20|26-33] ///hide #include
#include
///unhide struct MovableBuffer { size_t m_size; int* m_pArray; MovableBuffer(size_t size = 0); ~MovableBuffer() { delete[] m_pArray; } // copy constructor MovableBuffer(const MovableBuffer& other); // move constructor MovableBuffer(MovableBuffer&& other) : m_size(other.m_size) , m_pArray(other.m_pArray) { other.m_size = 0; other.m_pArray = nullptr; } // copy assignment operator MovableBuffer& operator=( const MovableBuffer& other); // move assignment operator MovableBuffer& operator=(MovableBuffer&& other) { MovableBuffer tmp(other); std::swap(m_size, tmp.m_size); std::swap(m_pArray, tmp.m_pArray); return *this; } }; ``` --- # [Let's benchmark](http://quick-bench.com/ryKzdF90wADpRQ6hHuvUvhat1Z0) --- ## Move only ```cpp [|20] ///fails=use of deleted function 'constexpr MoveOnlyBuffer::MoveOnlyBuffer(const MoveOnlyBuffer&)' ///hide #include
#include
///unhide struct MoveOnlyBuffer { size_t m_size; int* m_pArray; MoveOnlyBuffer(size_t size = 0); ~MoveOnlyBuffer() { delete[] m_pArray; } MoveOnlyBuffer(MoveOnlyBuffer&& other) : m_size(other.m_size) , m_pArray(other.m_pArray) { other.m_size = 0; other.m_pArray = nullptr; } MoveOnlyBuffer& operator=(MoveOnlyBuffer&& other) { MoveOnlyBuffer tmp(other); std::swap(m_size, tmp.m_size); std::swap(m_pArray, tmp.m_pArray); return *this; } }; ``` Note: - show compiler error - performance bug in the movable class --- ## `std::move` casts the argument to an `rvalue` ```cpp ///hide #include
///unhide namespace std { template< class T > typename remove_reference
::type&& move( T&& t ) noexcept { return static_cast
::type&&>(t); } } // namespace std ``` --- ## (really) move only ```cpp [20] ///hide #include
#include
///unhide struct MoveOnlyBuffer { size_t m_size; int* m_pArray; MoveOnlyBuffer(size_t size = 0); ~MoveOnlyBuffer() { delete[] m_pArray; } MoveOnlyBuffer(MoveOnlyBuffer&& other) : m_size(other.m_size) , m_pArray(other.m_pArray) { other.m_size = 0; other.m_pArray = nullptr; } MoveOnlyBuffer& operator=(MoveOnlyBuffer&& other) { MoveOnlyBuffer tmp(std::move(other)); std::swap(m_size, tmp.m_size); std::swap(m_pArray, tmp.m_pArray); return *this; } }; ``` --- # [Let's benchmark](http://quick-bench.com/mevho4kNsJTPbnA7S8zrFToi5Is) --- ## Exception safety
No-throw exception guarantee
the function never throws exceptions
Strong exception guarantee
If the function throws an exception, the state of the program is rolled back to the state just before the function call
Basic exception guarantee
If the function throws an exception, the program is in a valid state. No resources are leaked, and all objects' invariants are intact
No exception guarantee
--- `vector::push_back` pseudo code ```cpp ///fails=expected initializer before '<' token template
void vector
::push_back(const T& value) { if (size == capacity) { allocate larger buffer if (is movable) { // move all Ts to the new buffer } else { // copy all Ts to the new buffer } } // copy value to the end if the buffer } ``` --- ## copy
Old
ERROR
New
--- ## move
Old
ERROR
New
--- ## `noexcept` specifier A function can be decorated with `noexcept(expr)` specifier to indicate whether it will throw: ```cpp int* allocate_array_impl(int N){ return new int[N]; } template
int* allocate_array() noexcept(N >= 0) { return allocate_array_impl(N); } int* a = allocate_array<42>(); int* b = allocate_array<-1>(); ``` `noexcept` is a shortcut for `noexcept(true)`. --- ## `noexcept` operator `noexcept(expr)` returns `true` if `expr` is declared to not throw any exceptions. ```cpp template
void wrapper(Func func) noexcept(noexcept(func())) { func(); } ``` --- - destructors are implicitly `noexcept` - if a `noexcept` function throws, the function `std::terminate` is called ```cpp extern void f(); // potentially-throwing void g() noexcept { f(); // valid, even if f throws throw 42; // valid, effectively a call to std::terminate } ``` --- ## `noexcept` move ```cpp [14, 27-28] ///hide #include
#include
///unhide struct MovableBuffer { size_t m_size; int* m_pArray; MovableBuffer(size_t size = 0); ~MovableBuffer() { delete[] m_pArray; } // copy constructor MovableBuffer(const MovableBuffer& other); // move constructor MovableBuffer(MovableBuffer&& other) noexcept : m_size(other.m_size) , m_pArray(other.m_pArray) { other.m_size = 0; other.m_pArray = nullptr; } // copy assignment operator MovableBuffer& operator=( const MovableBuffer& other); // move assignment operator MovableBuffer& operator=( MovableBuffer&& other) noexcept { MovableBuffer tmp(std::move(other)); std::swap(m_size, tmp.m_size); std::swap(m_pArray, tmp.m_pArray); return *this; } }; ``` --- # [Let's benchmark](http://quick-bench.com/Po3o1F2MuAsyRNciCaOoAcnQbyY) --- ## `std::move` again ```cpp ///hide #include
///unhide template< class T > typename std::remove_reference
::type&& move( T&& t ) noexcept { return static_cast
::type&&>(t); } ``` We can also move `lvalue`s that are not needed anymore: ```cpp ///hide #include
#include
#include
void foo() { ///unhide std::vector
inputs; for (std::string s; std::cin >> s;) { inputs.push_back(std::move(s)); } ///hide } ``` Note: But `s` is an `lvalue`!! --- ## reference collapsing ```cpp ///hide #include
///unhide template
using lref = T&; template
using rref = T&&; static_assert(std::is_same
>, int&>::value, "& * & = &"); static_assert(std::is_same
>, int&>::value, "& * && = &"); static_assert(std::is_same
>, int&>::value, "&& * & = &"); static_assert(std::is_same
>, int&&>::value, "&& * && = &&"); ``` --- ## forwarding ref calling `std::move(s)` instantiates ```cpp ///fails='move' in namespace 'std' does not name a template type std::move
(std::string & && t) ``` i.e. ```cpp ///fails='move' in namespace 'std' does not name a template type std::move
(std::string & t) ``` - `template
(T&&)` just retains the type of the call site argument - such a reference is called a **forwarding reference** --- ## `std::forward` forwards the argument to another function with the value category it had when passed to the calling function. ```cpp ///hide #include
///unhide template
auto trace(Func&& f, T&& t) { std::cout << "Calling f on " << t; return f(std::forward
(t)); } ``` --- ## Special member functions | | | | |-|-|-| |1.|default constructor|`T()`| |2.|copy constructor|`T(const T&)`| |3.|move constructor|`T(T&&)`| |4.|destructor|`~T()`| |5.|copy assignment|`T& operator=(const T&)`| |6.|move assignment|`T& operator=(T&&)`| **Special** - compiler generated, under certain circumstances --- ![special members](02_move/special_members.jpg) Source: [Howard Hinnant](https://howardhinnant.github.io/classdecl.html) --- ## deleted function e.g. to prevent narrowing conversions: ```cpp ///fails=use of deleted function 'void foo(int)' void foo(short i); void foo(int i) = delete; ///hide void bar() { ///unhide foo(static_cast
(42)); // ok foo(42); // error, deleted function ///hide } ``` --- ## defaulted special member function can force the compiler to generate ```cpp ///hide #include
///unhide struct T { T() = default; T(int i); T(T&&); T(const T&) = default; }; static_assert(std::is_default_constructible
::value, "default"); static_assert(std::is_copy_constructible
::value, "copy"); ``` --- ## rule of 0 > If you can avoid defining default operations, do --- ## rule of 5(6) > If you define or =delete any default operation, define or =delete them all --- ## class prototypes:
```cpp class normal { public: // rule of zero }; ``` ```cpp class container { public: container() noexcept; ~container() noexcept; container(const container& other); container(container&& other) noexcept; container& operator=(const container& other); container& operator=(container&& other) noexcept; }; ``` ```cpp class resource_handle { public: resource_handle() noexcept; ~resource_handle() noexcept; resource_handle(resource_handle&& other) noexcept; resource_handle& operator=(resource_handle&& other) noexcept; resource_handle(const resource_handle&) = delete; resource_handle& operator=(const resource_handle&) = delete; }; ``` ```cpp class immoveable { public: immoveable() noexcept; ~immoveable() noexcept; immoveable(const immoveable&) = delete; immoveable& operator=(const immoveable&) = delete; immoveable(immoveable&&) = delete; immoveable& operator=(immoveable&&) = delete; }; ```
Source: [Jonathan Müller](https://foonathan.net/2019/02/special-member-functions/) --- # Thank you ---- --- Take a look at the following code: ```cpp [|4-6,8-10|1,22-27|8,16,20-21] ///options=-std=c++03 struct POD { int i; float f; }; class C { POD p; int iarr[3]; double d; public: C() : d(3.14) { p.i=2; p.f=22.34; for (unsigned i = 0; i < 3; ++i) iarr[i] = i; } }; class D { public: D(C const&, POD const&) {} }; int main() { C c(); D d(C(), POD()); POD* pp = new POD(); pp->i = 4; pp->f = 22.1; float* pf = new float[2]; pf[0] = 1.2f; pf[1] = 2.3f; } ``` --- ## Most vexing parse ```cpp ///hide struct C{}; struct D{}; struct POD{}; ///unhide C c(); // c: () => C D d(C(), POD()); // d: (() => C, () => POD) => D ``` --- ## Different initialization syntax ```cpp [] ///hide int v = 7; typedef int X; ///unhide X t1 = v; // copy initialization X t2(v); // direct initialization X t3 = { v }; // initialize using initializer list X t4 = X(v); // make an X from v and copy it to t4 ``` --- ## Different initialization syntax ```cpp [] int v = 7; typedef int X; X t1 = v; // ok X t2(v); // ok X t3 = { v }; // ok X t4 = X(v); // ok ``` --- ## Different initialization syntax ```cpp [] ///fails=conversion from 'int' to non-scalar type 'X' requested int v = 7; typedef struct { int x; int y; } X; X t1 = v; // error X t2(v); // error X t3 = { v }; // ok: X is an aggregate X t4 = X(v); // error: we can’t cast an int to a struct ``` --- ## Different initialization syntax ```cpp [] ///fails=conversion from 'int' to non-scalar type 'X' {aka 'std::vector
'} requested ///hide #include
///unhide int v = 7; typedef std::vector
X; X t1 = v; // error: constructor is explicit X t2(v); // ok X t3 = { v }; // error: not an aggregate X t4 = X(v); // ok (make an X from v and copy it to t4) ``` --- ## Different initialization syntax ```cpp [] ///fails=invalid conversion from 'int' to 'X' {aka 'int*'} int v = 7; typedef int* X; X t1 = v; // error X t2(v); // error X t3 = { v }; // error X t4 = X(v); // ok: unfortunately converts int to an int* ``` --- ## More initialization ```cpp ///hide struct X { X(int); }; struct Y : X { Y(int); int m; }; int v; void f1() { ///unhide X(42); // create a temporary ///hide } ///unhide X f(int v) { return v; } // return a value ///hide void f2() { ///unhide void g(X); g(v); // pass an argument new X(v); // create object on free store ///hide } ///unhide Y::Y(int v) :X(v), m(v) {} // base and member initializers ///hide void f3() { ///unhide throw v; // throw an exception ///hide } ``` --- # List Initialization
Sources: - [simplify C++](https://arne-mertz.de/2015/07/new-c-features-uniform-initialization-and-initializer_list/) - [N2215](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2215.pdf)
--- ## Initialize (almost) anything with `{}` ```cpp ///hide struct X { X(int); }; struct Y : X { Y(int); int m; }; int v; void f1() { ///unhide X{42}; // create a temporary ///hide } ///unhide X f(int v) { return {v}; } // return a value ///hide void f2() { ///unhide void g(X); g({v}); // pass an argument new X{v}; // create object on free store ///hide } ///unhide Y::Y(int v) :X{v}, m{v} {} // base and member initializers ``` This is still forbidden: ```cpp ///fails=expected primary-expression before '{' token ///hide void f3() { int v = 42; ///unhide throw {v}; // throw an exception ///hide } ``` --- # It's also safer ```cpp ///fails=narrowing conversion int i1(4.2); // no problem int i2{4.2}; // error: narrowing conversion double d; float f1(d); // no problem float f2{d}; // error: narrowing conversion float f3(i1); // no problem float f4{i1}; // error: narrowing conversion unsigned int ui1(-1); // no problem unsigned int ui2{-1}; // error: narrowing conversion ``` --- ## Now, with list initialization ```cpp [8,17-20] struct POD { int i; float f; }; class C { POD p; int iarr[3]; double d; public: C() : p{2, 22.34}, iarr{0, 1, 2}, d{3.14} {} }; class D { public: D(C const&, POD const&) {} }; int main() { C c{}; D d{C(), POD()}; POD* pp = new POD{4, 22.1}; float* pf = new float[2]{1.2f, 2.3f}; } ``` --- ## Creating an array ```cpp ///options=-std=c++03 ///hide #include
int main(){ ///unhide std::string days[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; ///hide } ``` --- ## Creating a vector ```cpp ///options=-std=c++03 ///hide #include
#include
int main(){ ///unhide std::vector
days; days.reserve(7); days.push_back("Sunday"); days.push_back("Monday"); days.push_back("Tuesday"); days.push_back("Wednesday"); days.push_back("Thursday"); days.push_back("Friday"); days.push_back("Saturday"); ///hide } ``` --- ### `std::initializer_list
` - A lightweight proxy object that provides access to an array of objects of type const T. - Constructed automatically by the compiler when - calling a constructor/function accepting `std::initializer_list` - binding a `braced-init-list` to auto --- ## Example ```cpp [|4,16|10,17] ///hide #include
#include
///unhide template
struct S { std::vector
v; S(std::initializer_list
l) : v(l) { std::cout << "constructed with a " << l.size() << "-element list\n"; } void append(std::initializer_list
l) { v.insert(end(v), begin(l), end(l)); } }; int main() { S
s = {1, 2, 3, 4, 5}; s.append({6, 7, 8}); } ``` --- ## and ```cpp ///hide #include
#include
int main(){ ///unhide std::vector
days { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; ///hide } ``` --- ## or even ```cpp ///hide #include
#include
int main() { std::string s1; ///unhide std::map
m = { {1, "a"}, {2, {'a', 'b', 'c'} }, {3, s1} }; ///hide } ``` --- ## `initializer_list` constructor is preferred ```cpp ///hide #include
#include
int main() { ///unhide std::vector
aDozenOfFives(12, 5); std::vector
twelveAndFive{12, 5}; assert(aDozenOfFives != twelveAndFive); ///hide } ``` --- ## Range-based for loop Instead of ```cpp [2-4] ///hide #include
#include
///unhide void print(const std::vector
& v){ for (auto it = begin(v); it != end(v); ++it) { std::cout << *it << ' '; } std::cout << '\n'; } ``` write ```cpp [2-4] ///hide #include
#include
///unhide void print(const std::vector
& v){ for (const auto& i: v) { std::cout << i << ' '; } std::cout << '\n'; } ``` --- ## with `std::initializer_list` ```cpp ///hide #include
int main(){ ///unhide for (auto x : {-1, -2, -3}) std::cout << x << ' '; std::cout << '\n'; ///hide } ``` --- ## Note ```cpp [7-13] ///hide #include
#include
///unhide template
void hasType(const Actual &) { static_assert(std::is_same
::value, "should be the same"); } ///hide int main() { ///unhide int i1{42}; auto i2{42}; hasType
(i2); int i3 = {42}; auto i4 = {42}; hasType
>(i4); ///hide } ``` Note: This initialization syntax is frequently referred to as "Uniform initialization". For these reasons, it is not always advised to use this syntax. --- ## note a *braced-init-list* does not have a type in itself: ```cpp ///fails=no matching function for call to 'do_sth(
)' template
void do_sth(T t); ///hide void foo() { ///unhide do_sth({1, 2, 3, 4, 5}); // error: couldn't infer template argument ///hide } ``` --- ## note Does not support moves
```cpp ///hide #include
#include
///unhide struct S { S() { std::cout << "default\n"; } S(const S&) { std::cout << "copy\n"; } S(S&&) { std::cout << "move\n"; } }; ///hide int main() { ///unhide std::vector
v{S(), S(), S()}; ///hide } ``` ```cpp ///compiler=g83 ///fails=use of deleted function 'S::S(const S&)' ///hide #include
#include
///unhide struct S { S() { std::cout << "default\n"; } S(const S&) = delete; S(S&&) { std::cout << "move\n"; } }; ///hide void foo() { ///unhide std::vector
v{S(), S(), S()}; // error ///hide } ```
--- ---- --- ## from Boost::Thread library ```cpp ///libs=boost:173 ///hide #include
#include
#include
///unhide namespace boost { class thread { ///hide boost::detail::thread_data_ptr thread_info; struct dummy; void start_thread(); ///unhide template
thread(F f,A1 a1, typename disable_if
, dummy* >::type=0): thread_info(make_thread_info(boost::bind(boost::type
(),f,a1))) { start_thread(); } template
thread(F f,A1 a1,A2 a2): thread_info(make_thread_info(boost::bind(boost::type
(),f,a1,a2))) { start_thread(); } template
thread(F f,A1 a1,A2 a2,A3 a3): thread_info(make_thread_info(boost::bind(boost::type
(),f,a1,a2,a3))) { start_thread(); } template
thread(F f,A1 a1,A2 a2,A3 a3,A4 a4): thread_info(make_thread_info(boost::bind(boost::type
(),f,a1,a2,a3,a4))) { start_thread(); } template
thread(F f,A1 a1,A2 a2,A3 a3,A4 a4,A5 a5): thread_info(make_thread_info(boost::bind(boost::type
(),f,a1,a2,a3,a4,a5))) { start_thread(); } template
thread(F f,A1 a1,A2 a2,A3 a3,A4 a4,A5 a5,A6 a6): thread_info(make_thread_info(boost::bind(boost::type
(),f,a1,a2,a3,a4,a5,a6))) { start_thread(); } template
thread(F f,A1 a1,A2 a2,A3 a3,A4 a4,A5 a5,A6 a6,A7 a7): thread_info(make_thread_info(boost::bind(boost::type
(),f,a1,a2,a3,a4,a5,a6,a7))) { start_thread(); } template
thread(F f,A1 a1,A2 a2,A3 a3,A4 a4,A5 a5,A6 a6,A7 a7,A8 a8): thread_info(make_thread_info(boost::bind(boost::type
(),f,a1,a2,a3,a4,a5,a6,a7,a8))) { start_thread(); } template
thread(F f,A1 a1,A2 a2,A3 a3,A4 a4,A5 a5,A6 a6,A7 a7,A8 a8,A9 a9): thread_info(make_thread_info(boost::bind(boost::type
(),f,a1,a2,a3,a4,a5,a6,a7,a8,a9))) { start_thread(); } }; } // namespace boost ``` --- ## common `printf` bug ```cpp ///compiler=g75 #include
#include
std::string getName() { return "Dvir"; } ///hide int main() { ///unhide printf("Hello %s", getName()); ///hide } ``` --- ## Variadic templates
Sources: - [N2080](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2080.pdf) - [cppreference](https://en.cppreference.com/w/cpp/language/parameter_pack) - [Fluent{C++}](https://www.fluentcpp.com/2019/02/05/how-to-define-a-variadic-number-of-arguments-of-the-same-type-part-3/)
--- ## variadic type
```cpp template
struct count; ```
parameter pack
--- ## base specialization ```cpp ///hide #include
template
struct count; ///unhide template<> struct count<> { static const size_t value = 0; }; ``` --- ## recursive case
```cpp ///hide #include
template
struct count; ///unhide template
struct count
{ static const size_t value = 1 + count
::value; }; ```
pack expansion
--- ## check ```cpp [14-15] ///hide #include
///unhide template
struct count; template<> struct count<> { static const size_t value = 0; }; template
struct count
{ static const size_t value = 1 + count
::value; }; static_assert(count
::value == 3, "3 elements"); ``` --- ## `sizeof...` ```cpp ///hide #include
///unhide template
struct count { static const size_t value = sizeof...(Args); }; static_assert(count
::value == 3, "3 elements"); ``` Note: Args is not expanded --- ## type safe `printf` ```cpp [] ///hide #include
///unhide template
void tprintf(const char* format, T value, Args... args) { for ( ; *format != '\0'; format++ ) { if ( *format == '%' ) { std::cout << value; tprintf(format+1, args...); // recursive call return; } std::cout << *format; } } ``` --- ## with recursion base ```cpp [] ///hide #include
///unhide void tprintf(const char* format) { std::cout << format; } template
void tprintf(const char* format, T value, Args... args) { for ( ; *format != '\0'; format++ ) { if ( *format == '%' ) { std::cout << value; tprintf(format+1, args...); // recursive call return; } std::cout << *format; } } ``` --- ## without copy ```cpp [] ///hide #include
///unhide void tprintf(const char* format) { std::cout << format; } template
void tprintf(const char* format, T value, const Args&... args) { for ( ; *format != '\0'; format++ ) { if ( *format == '%' ) { std::cout << value; tprintf(format+1, args...); // recursive call return; } std::cout << *format; } } ``` --- ## compiler generated "recursion" ```cpp ///hide #include
void tprintf(const char* format) { std::cout << format; } template
void tprintf(const char* format, T value, const Args&... args) { for ( ; *format != '\0'; format++ ) { if ( *format == '%' ) { std::cout << value; tprintf(format+1, args...); // recursive call return; } std::cout << *format; } } ///unhide int main() { tprintf("% world% %\n", "Hello", '!', 123); // compiler generates // tprintf("% world% %\n", "Hello", '!', 123); // tprintf(" world% %\n", '!', 123); // tprintf(" %\n", 123); // tprintf("\n"); } ``` --- ## new `boost::thread` ```cpp ///libs=boost:173 ///hide #include
#include
#include
///unhide namespace boost { class thread { ///hide boost::detail::thread_data_ptr thread_info; struct dummy; void start_thread(); ///unhide template
thread(F&& f, Arg&& arg, Args&&... args) : thread_info(make_thread_info( thread_detail::decay_copy(boost::forward
(f)), thread_detail::decay_copy(boost::forward
(arg)), thread_detail::decay_copy(boost::forward
(args))...) ) { start_thread(); } }; } // namespace boost ``` --- ## more pack expansions ```cpp [1|2|3|4-7|8-9] ///hide template
void f(Args...); template
void h(Args...); template
void foo(Args... args) { auto n = 0; ///unhide f(&args...); // f(&E1, &E2, &E3) f(n, ++args...); // f(n, ++E1, ++E2, ++E3); f(++args..., n); // f(++E1, ++E2, ++E3, n); f(const_cast
(&args)...); // f(const_cast
(&X1), // const_cast
(&X2), // const_cast
(&X3)) f(h(args...) + args...); // f(h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3) ///hide } ``` Note: last is nested --- ## non-type template parameter pack ```cpp template
void g(Ts (&...arr)[N]) {} ///hide int main() { ///unhide int n[1]; g("a", n); // Ts (&...arr)[N] expands to // const char (&)[2], int(&)[1] ///hide } ``` --- ## Base specifiers and member initializer lists ```cpp template
class X : public Mixins... { public: X(const Mixins&... mixins) : Mixins(mixins)... { } }; ``` --- ## `std::tuple` a variadic pair ```cpp template< class... Types > class tuple; ``` --- ## return multiple values ```cpp [|1|3|9-13] ///hide #include
#include
#include
///unhide std::tuple
get_student(int id) { if (id == 0) return std::make_tuple(3.8, 'A', "Lisa Simpson"); if (id == 1) return std::make_tuple(2.9, 'C', "Milhouse Van Houten"); if (id == 2) return std::make_tuple(1.7, 'D', "Ralph Wiggum"); throw std::invalid_argument("id"); } ///hide int main() { ///unhide auto student0 = get_student(0); std::cout << "ID: 0, " << "GPA: " << std::get<0>(student0) << ", " << "grade: " << std::get<1>(student0) << ", " << "name: " << std::get<2>(student0) << '\n'; ///hide } ``` --- ## `std::tie` ```cpp [9-16] ///hide #include
#include
#include
///unhide std::tuple
get_student(int id) { if (id == 0) return std::make_tuple(3.8, 'A', "Lisa Simpson"); if (id == 1) return std::make_tuple(2.9, 'C', "Milhouse Van Houten"); if (id == 2) return std::make_tuple(1.7, 'D', "Ralph Wiggum"); throw std::invalid_argument("id"); } ///hide int main() { ///unhide double gpa1; char grade1; std::string name1; std::tie(gpa1, grade1, name1) = get_student(1); std::cout << "ID: 1, " << "GPA: " << gpa1 << ", " << "grade: " << grade1 << ", " << "name: " << name1 << '\n'; ///hide } ``` Note: creates a tuple of references --- ## `std::ignore` ```cpp [|3] ///hide #include
#include
#include
#include
int main() { ///unhide std::set
set_of_str; bool inserted = false; std::tie(std::ignore, inserted) = set_of_str.insert("Test"); if (inserted) { std::cout << "Value was inserted successfully\n"; } ///hide } ``` --- ## avoiding recursion ```cpp ///hide #include
///unhide int sum() { return 0; } template
int sum(const T& t, const Args&... args) { return t + sum(args...); } ///hide int main() { ///unhide assert(sum(1, 2, 3) == 6); ///hide } ``` --- ## `std::initializer_list` for the rescue ```cpp ///hide #include
#include
///unhide template
int sum(const Args&... args) { auto res = 0; (void)std::initializer_list
{(res += args, 0)...}; return res; } ///hide int main() { ///unhide assert(sum(1, 2, 3) == 6); ///hide } ``` Note: C++17 has fold expressions --- # variadic arguments of the same type --- ## Solution #1 `std::initializer_list` ```cpp ///hide #include
#include
///unhide int sum(std::initializer_list
ints); ///hide void foo() { ///unhide assert(sum({1, 2, 3}) == 6); // sum({1, 2, "3"}); // fails to compile ///hide } ``` --- ```cpp #include
template
struct conjunction : std::true_type { }; template
struct conjunction
: B1 { }; template
struct conjunction
: std::conditional
, B1>::type {}; ``` Note: in the standard from C++17 --- ## Solution #2 `static_assert` ```cpp ///hide #include
#include
template
struct conjunction : std::true_type { }; template
struct conjunction
: B1 { }; template
struct conjunction
: std::conditional
, B1>::type {}; ///unhide template
int sum(const Args&... args) { static_assert(conjunction
...>::value, "all arguments should be int"); } ///hide void foo() { ///unhide assert(sum(1, 2, 3) == 6); // sum(1, 2, "3"); // fails to compile ///hide } ``` --- ## SFINAE "Substitution Failure Is Not An Error" ```cpp [|2,9|5,9] template