demystifying value categories in c
play

Demystifying Value Categories in C++ iCSC 2020 Nis Meinert Rostock - PowerPoint PPT Presentation

Demystifying Value Categories in C++ iCSC 2020 Nis Meinert Rostock University Disclaimer Disclaimer This talk is mainly about hounding (unnecessary) copy ctors In case you dont care: If youre not at all interested in


  1. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Understanding References Nis Meinert – Rostock University 20 19 18 17 16 15 14 13 1 11 10 12 11 / 100 2 7 3 6 4 5 8 #include <iostream> struct S { int x; S( int x): x(x) { std::cout << 'a'; } S( const S& other): x(other.x) { std::cout << 'b'; } S& operator =( const S& other) { x = other.x; std::cout << 'c'; return * this ; } }; void swap(S*& a, S*& b) { S* tmp = a; a = b; b = tmp; } int main() { S a{1}; S b{2}; swap(&a, &b); std::cout << a.x << b.x; } godbolt.org/z/Eh656x

  2. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Understanding References Nis Meinert – Rostock University 20 19 18 17 16 15 14 13 12 11 10 11 / 100 4 1 7 6 2 3 5 8 error: cannot bind non-const lvalue reference of type “ S*& ” to an rvalue of type “ S* ” #include <iostream> struct S { int x; S( int x): x(x) { std::cout << 'a'; } S( const S& other): x(other.x) { std::cout << 'b'; } S& operator =( const S& other) { x = other.x; std::cout << 'c'; return * this ; } }; void swap(S*& a, S*& b) { S* tmp = a; a = b; b = tmp; } int main() { S a{1}; S b{2}; swap(&a, &b); std::cout << a.x << b.x; } godbolt.org/z/Eh656x

  3. Value Categories

  4. Value categories with Venn diagrams Nis Meinert – Rostock University Demystifying Value Categories in C++ – Value Categories 12 / 100 (diagrams shamelessly stolen from bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html )

  5. Value categories with Venn diagrams 8 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 18 17 16 15 14 13 12 11 9 10 7 5 1 2 3 4 13 / 100 6 (diagrams shamelessly stolen from bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html ) struct S{ int x; }; S make_S( int x) { S s{.x = x}; return s; // has no name after returning } int main() { S a = make_S(42); // `a` is an lvalue // initialized with a prvalue S b = std::move(a); // prepare to die, `a`! // now `a` became an xvalue auto x = a.x; // ERROR: `a` is in an undefined state a = make_S(13); x = a.x; // fine! }

  6. Value categories with Venn diagrams 8 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 18 17 16 15 14 13 12 11 9 10 7 5 1 2 3 4 13 / 100 6 (diagrams shamelessly stolen from bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html ) struct S{ int x; }; S make_S( int x) { S s{.x = x}; return s; // has no name after returning } int main() { S a = make_S(42); // `a` is an lvalue // initialized with a prvalue S b = std::move(a); // prepare to die, `a`! // now `a` became an xvalue auto x = a.x; // ERROR: `a` is in an undefined state a = make_S(13); x = a.x; // fine! }

  7. Value categories with Venn diagrams 8 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 18 17 16 15 14 13 12 11 9 10 7 5 1 2 3 4 13 / 100 6 (diagrams shamelessly stolen from bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html ) struct S{ int x; }; S make_S( int x) { S s{.x = x}; return s; // has no name after returning } int main() { S a = make_S(42); // `a` is an lvalue // initialized with a prvalue S b = std::move(a); // prepare to die, `a`! // now `a` became an xvalue auto x = a.x; // ERROR: `a` is in an undefined state a = make_S(13); x = a.x; // fine! }

  8. Binding references to temporaries 7 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University extension) → …except it is a const reference (lifetime has a name… → One cannot refer to something that doesn’t → Memory addresses are always rvalues! 8 14 / 100 6 5 4 3 2 1 error: cannot bind non-const lvalue reference of type “ S*& ” to an rvalue of type “ S* ” template < typename T> void swap(T& a, T& b) { ... } int main() { S a{1}; S b{2}; swap(&a, &b); }

  9. std::move

  10. 15 / 100 8 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University → Syntax: 13 12 11 1 9 10 7 4 2 6 3 5 std::move #include <iostream> #include <utility> struct S{}; → std::move creates xvalues void f( const S&) { std::cout << 'a'; } void f(S&&) { std::cout << 'b'; } int main() { → lvalue ref.: S& S s; → rvalue ref.: S&& f(s); // prints 'a' f(std::move(s)); // prints 'b' } godbolt.org/z/aKbGEc

  11. Q: What is the output of the program? 8 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 15 14 13 12 11 10 1 9 7 5 2 3 4 16 / 100 6 #include <iostream> #include <utility> struct S{ S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; int main() { S s1; S s2(s1); S s3(S{}); S s4(std::move(s1)); } godbolt.org/z/16hYbz

  12. 17 / 100 elision (initializer is prvalue of the 11 13 10 9 14 8 15 7 6 12 same class type) 5 forced move construction 4 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Value Categories 1 A: abac #include <iostream> #include <utility> struct S{ → S s1 : no surprise S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } → S s2(s1) : no surprise S(S&&) { std::cout << 'c'; } }; → S s3(S{}) : mandatory copy int main() { S s1; S s2(s1); → S s4(std::move(s1)) : S s3(S{}); S s4(std::move(s1)); } godbolt.org/z/16hYbz

  13. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 19 18 17 16 15 14 13 12 1 11 10 18 / 100 4 7 2 6 5 3 8 #include <iostream> #include <utility> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; void f( const S&) { std::cout << '1'; } void f(S&) { std::cout << '2'; } void f(S&&) { std::cout << '3'; } int main() { S s1; f(s1); f(S{}); f(std::move(s1)); } godbolt.org/z/4MKojT

  14. 18 / 100 7 1 11 14 10 9 15 8 16 17 13 6 18 5 19 4 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Value Categories 12 A: a2a33 #include <iostream> #include <utility> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; void f( const S&) { std::cout << '1'; } void f(S&) { std::cout << '2'; } void f(S&&) { std::cout << '3'; } int main() { S s1; f(s1); f(S{}); f(std::move(s1)); } godbolt.org/z/4MKojT

  15. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 19 18 17 16 15 14 13 12 1 11 10 19 / 100 4 7 2 6 5 3 8 #include <iostream> #include <utility> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; void f( const S&) { std::cout << '1'; } void f(S) { std::cout << '2'; } void f(S&&) { std::cout << '3'; } int main() { S s1; f(s1); f(S{}); f(std::move(s1)); } godbolt.org/z/jaYTYP

  16. Q: What is the output of the program? 10 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University lvalue, nor rvalue) copy and reference overloads! (neither Compile-time error (in all three cases) 19 18 17 16 15 1 13 12 11 14 9 7 2 3 4 6 5 20 / 100 8 #include <iostream> #include <utility> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } → f(s1) : ambiguity between 2 and 1 }; → f(S{}) : ambiguity between 2 and 3 void f( const S&) { std::cout << '1'; } void f(S) { std::cout << '2'; } → f(std::move(s1) : same as f(S) void f(S&&) { std::cout << '3'; } → compiler cannot difgerentiate between ֒ int main() { S s1; f(s1); f(S{}); f(std::move(s1)); } godbolt.org/z/jaYTYP

  17. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 18 17 16 15 14 13 12 11 1 10 21 / 100 4 8 7 6 2 5 3 #include <iostream> #include <utility> struct S { ~S() { std::cout << 'a'; } }; void f( const S&) { std::cout << '1'; } void f(S&) { std::cout << '2'; } void f(S&&) { std::cout << '3'; } int main() { S&& r1 = S{}; f(r1); S&& r2 = S{}; f(std::move(r2)); } godbolt.org/z/5s1zc5

  18. 22 / 100 9 15 18 14 1 13 anymore and which will die soon (cf. 12 11 lifetime extension!) 10 but makes the object look like a dying object 17 8 7 An rvalue has no name 6 NB: an rvalue ref behaves like an lvalue ref except that it can bind to a temporary (an rvalue), whereas one 5 cannot bind a (non const) lvalue ref to an rvalue. 4 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Value Categories 16 A: 23aa #include <iostream> → S&& : object that nobody cares about #include <utility> struct S { ~S() { std::cout << 'a'; } }; → std::move does not actually kill, void f( const S&) { std::cout << '1'; } void f(S&) { std::cout << '2'; } void f(S&&) { std::cout << '3'; } int main() { S&& r1 = S{}; f(r1); S&& r2 = S{}; f(std::move(r2)); } godbolt.org/z/5s1zc5

  19. 23 / 100 1 Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University argument to an rvalue → unconditionally casts its runtime → does nothing at all during → does not destroy → does not move 7 5 6 4 3 2 std::move So what does std::move ? #include <type_traits> template < typename T> decltype ( auto ) move(T&& t) { using R = std::remove_reference_t<T>&&; return static_cast <R>(t); } godbolt.org/z/W8zb8G

  20. 24 / 100 6 1 3 8 4 7 5 6 5 2 7 4 8 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Value Categories 1 Quick Bench: tinyurl.com/y67sg7to std::vector< int > x(1000, 42); std::vector< int > y(1000, 42); for ( auto _ : state) { auto tmp = x; x = y; y = tmp; benchmark::DoNotOptimize(x[345] + y[678]); } std::vector< int > x(1000, 42); std::vector< int > y(1000, 42); for ( auto _ : state) { auto tmp = std::move(x); x = std::move(y); y = std::move(tmp); benchmark::DoNotOptimize(x[345] + y[678]); }

  21. Demystifying Value Categories in C++ – Value Categories Nis Meinert – Rostock University 25 / 100 Quick Bench: tinyurl.com/y67sg7to

  22. Universal References

  23. Rvalue ref. or no rvalue ref.? Rvalue refs are declared using “&&”: reasonable to assume that the presence of “&&” in Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 26 / 100 a type declaration indicates an rvalue reference? struct S{}; S&& s = S{}; // (1) Does “ && ” mean rvalue reference? → (1) : ??? auto && s2 = s; // (2) → (2) : ??? void f(S&& s); // (3) → (3) : ??? template < typename T> void f(T&& t); // (4) → (4) : ??? → (5) : ??? template < typename T> void f( const T&& t); // (5) → (6) : ??? template < typename T> void f(std::vector<T>&& v); // (6)

  24. Rvalue ref. or no rvalue ref.? Rvalue refs are declared using “&&”: reasonable to assume that the presence of “&&” in Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 26 / 100 a type declaration indicates an rvalue reference? No! struct S{}; S&& s = S{}; // (1) Does “ && ” mean rvalue reference? → (1) : yes auto && s2 = s; // (2) → (2) : no void f(S&& s); // (3) → (3) : yes template < typename T> void f(T&& t); // (4) → (4) : no → (5) : yes ⋆ template < typename T> void f( const T&& t); // (5) → (6) : yes template < typename T> void f(std::vector<T>&& v); // (6) ⋆ albeit questionable: move changes object in most cases �↔ const

  25. 27 / 100 6 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 12 11 10 9 7 8 3 4 1 2 5 std::move and const ⋆ albeit questionable: move changes object in most cases �↔ const #include <iostream> struct S { S() {} S( const S&) { std::cout << 'A'; } S(S&&) { std::cout << 'B'; } }; int main() { const S s; auto s2 = std::move(s); } godbolt.org/z/r9hv8K …prints A (cf. https://stackoverflow.com/a/28595415 )

  26. Universal references reduce: Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University references! Universal reference are always 28 / 100 → Rule of thumb: substitute fully Universal references † std::vector<S> v; → Syntax ( x is a universal reference): auto && s = v[0]; // S&&& -> S& → auto&& x auto && s2 = S{}; // S&&&& -> S&& → template <typename T> auto && s3 = s2; // S&&& -> S& f(T&& x… // S&&&& -> S&& auto && s3 = std::move(s2); qualified type into auto or T and /* Exception */ S s4{}; auto && s5 = s4; // S&& -> S& → && �→ && → &&& �→ & → &&&& �→ && † Universal reference : term introduced by Scott Meyers

  27. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 17 16 15 14 13 12 11 1 10 29 / 100 8 3 7 6 5 2 4 #include <iostream> #include <type_traits> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; template < typename T> S f(T&& t) { return t; } int main() { S s{}; f(s); f(std::move(s)); } godbolt.org/z/6xn1n3

  28. 29 / 100 16 11 1 10 9 14 8 15 7 6 12 17 5 …how to preserve the value category? 4 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Universal References 13 A: abb #include <iostream> #include <type_traits> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; template < typename T> S f(T&& t) { return t; } int main() { S s{}; f(s); f(std::move(s)); } godbolt.org/z/6xn1n3

  29. Q: What is the output of the program? 10 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 19 18 17 16 15 14 13 12 1 11 30 / 100 4 8 7 2 6 5 3 9 #include <iostream> #include <type_traits> struct S{}; void f(S&) { std::cout << 'a'; } void f(S&&) { std::cout << 'b'; } int main() { auto && r1 = S{}; static_assert(std::is_same_v< decltype (r1), S&&>); f(r1); f( static_cast <S&&>(r1)); S s; auto && r2 = s; static_assert(std::is_same_v< decltype (r2), S&>); f(r2); } godbolt.org/z/zTExze

  30. 30 / 100 17 12 1 11 15 10 16 9 8 7 13 18 6 5 19 4 3 Nis Meinert – Rostock University 2 Demystifying Value Categories in C++ – Universal References 14 A: aba #include <iostream> #include <type_traits> struct S{}; void f(S&) { std::cout << 'a'; } void f(S&&) { std::cout << 'b'; } int main() { auto && r1 = S{}; static_assert(std::is_same_v< decltype (r1), S&&>); f(r1); f( static_cast <S&&>(r1)); S s; auto && r2 = s; static_assert(std::is_same_v< decltype (r2), S&>); f(r2); } godbolt.org/z/zTExze

  31. Q: What is the output of the program? 8 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 16 15 14 13 12 11 1 9 10 7 5 2 3 4 31 / 100 6 #include <iostream> struct S{ void f() & { std::cout << 'a'; } void f() && { std::cout << 'b'; } }; int main() { auto && r1 = S{}; r1.f(); static_cast < decltype (r1)>(r1).f(); auto && r2 = r1; r2.f(); static_cast < decltype (r2)>(r2).f(); } godbolt.org/z/WcYYsd

  32. 31 / 100 8 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 16 15 14 13 12 11 1 9 10 7 5 2 3 6 4 A: abaa #include <iostream> struct S{ void f() & { std::cout << 'a'; } void f() && { std::cout << 'b'; } }; int main() { auto && r1 = S{}; r1.f(); static_cast < decltype (r1)>(r1).f(); auto && r2 = r1; r2.f(); static_cast < decltype (r2)>(r2).f(); } godbolt.org/z/WcYYsd

  33. Perfect forwarding 7 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 6 5 4 3 2 1 How do we fuse these implementations? 8 9 32 / 100 3 6 1 5 2 4 // if `t` is an lvalue of type `T` template < typename T> T& forward(T& t) { return t; } // if `t` is an rvalue of type `T` template < typename T> T&& forward(T& t) { return std::move(t); // static_cast<T&&>(t) } #include <type_traits> template < typename T> T&& forward(std::remove_reference_t<T>& t) { return static_cast <T&&>(t); } godbolt.org/z/EjPnPr

  34. Q: What is the output of the program? 9 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 17 16 15 14 13 12 11 1 10 33 / 100 8 3 7 6 5 2 4 #include <iostream> #include <type_traits> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; template < typename T> S f(T&& t) { return std::forward<T>(t); } int main() { S s{}; f(s); f(std::move(s)); } godbolt.org/z/7Worb3

  35. 34 / 100 9 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University references 17 16 15 14 13 12 1 10 11 8 6 2 7 3 4 5 A: abc #include <iostream> #include <type_traits> struct S { S() { std::cout << 'a'; } S( const S&) { std::cout << 'b'; } S(S&&) { std::cout << 'c'; } }; template < typename T> S f(T&& t) { return std::forward<T>(t); } int main() { S s{}; f(s); f(std::move(s)); } godbolt.org/z/7Worb3 Rule of thumb: Use std::move for rvalues and std::forward for universal

  36. Q: Why can't we use perfect forwarding here? 6 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University 9 8 7 1 35 / 100 5 4 3 2 #include <functional> template < typename Iter, typename Callable, typename ... Args> void foreach (Iter current, Iter end, Callable op, const Args&... args) { while (current != end) { std::invoke(op, args..., *current); ++current; } } godbolt.org/z/TvnEfT

  37. Q: Why can't we use perfect forwarding here? 6 Demystifying Value Categories in C++ – Universal References Nis Meinert – Rostock University A: The first call in the loop might steal the values, leading to unexpected behavior 9 8 1 7 5 4 3 2 35 / 100 #include <functional> template < typename Iter, typename Callable, typename ... Args> void foreach (Iter current, Iter end, Callable op, const Args&... args) { while (current != end) { std::invoke(op, args..., *current); ++current; } } godbolt.org/z/TvnEfT calling op in subsequent iterations.

  38. Reading x86-64 Assembly …for fun and profit

  39. Function Prologue & Epilogue alternatively Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 2 1 alternatively 3 2 1 Epilogue → Few lines of code at the beginning ( prologue ) and end ( epilogue ) of a function, 1 36 / 100 Prologue → registers 2 which prepares (and eventually restores) 1 3 → the stack and and compilers) → Not part of assembly: convention (defined & interpreted difgerently by difgerent OS push rbp ; rbp: frame pointer mov rsp, rbp mov rbp, rsp ; rsp: stack pointer pop rbp sub rsp, N ret enter N, 0 leave ret (reserve N bytes on stack for local use)

  40. Stack frame for function call (stack frame for function call with 8 arguments and Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 37 / 100 → pass arguments 1 …6 in transfers control there ┌──────────────┐ │ ... │ │ 8th Argument │ (rbp + 24) │ 7th Argument │ (rbp + 16) → CALL = PUSH address of next ├──────────────┤ instruction + JMP target │ rip │ (return address) │ rbp │ (rbp) → RET pops return address and ├──────────────┤ │ rbx │ │ r12 │ registers ( rsi , rdx , …) │ r13 │ (rsp) └──────────────┘ local registers rbx , r12 and r13 )

  41. 38 / 100 Nis Meinert – Rostock University Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit lea vs. mov ┌──────────────────┐ │ Registers │ → lea : load efgective address ├──────────────────┤ → puts memory address from src into │ EAX = 0x00000000 │ the destination dest │ EBX = 0x00403A40 │ → Example: lea eax, [ebx+8] └──────────────────┘ ┌────────────┐ → put [ebx+8] into eax │ Memory │ → value of eax afuer instruction: ├────────────┤ 0x00403A48 0x00403A40 │ 0x7C81776F │ → …whereas: mov eax, [ebx+8] 0x00403A44 │ 0x7C911000 │ → value of eax afuer instruction: 0x00403A48 │ 0x0012C140 │ 0x0012C140 0x00403A4C │ 0x7FFDB000 │ └────────────┘

  42. Reading assembly for fun and profit 4 Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 1 39 / 100 2 3 int f( int x, int y, int z) { # g92 -O0 int sum = x + y + z; | f(int, int, int): return sum; 1| push rbp } 1| mov rbp, rsp 1| mov DWORD PTR [rbp-20], edi godbolt.org/z/MaWcP9 1| mov DWORD PTR [rbp-24], esi 1| mov DWORD PTR [rbp-28], edx 2| mov edx, DWORD PTR [rbp-20] 2| mov eax, DWORD PTR [rbp-24] 2| add edx, eax 2| mov eax, DWORD PTR [rbp-28] 2| add eax, edx 2| mov DWORD PTR [rbp-4], eax 3| mov eax, DWORD PTR [rbp-4] 4| pop rbp 4| ret godbolt.org/z/MaWcP9

  43. Reading assembly for fun and profit 4 Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 1 39 / 100 2 3 int f( int x, int y, int z) { # g92 -O0 int sum = x + y + z; | f(int, int, int): return sum; 1| push rbp } 1| mov rbp, rsp 1| mov DWORD PTR [rbp-20], edi godbolt.org/z/MaWcP9 1| mov DWORD PTR [rbp-24], esi 1| mov DWORD PTR [rbp-28], edx 2| mov edx, DWORD PTR [rbp-20] # g92 -O1 2| mov eax, DWORD PTR [rbp-24] | f(int, int, int): 2| add edx, eax 2| add edi, esi 2| mov eax, DWORD PTR [rbp-28] 2| lea eax, [rdi+rdx] 2| add eax, edx 4| ret 2| mov DWORD PTR [rbp-4], eax 3| mov eax, DWORD PTR [rbp-4] godbolt.org/z/67WsqT 4| pop rbp 4| ret godbolt.org/z/MaWcP9

  44. Reading assembly for fun and profit 5 Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 1 7 6 40 / 100 4 3 2 int f( int x) { # g92 -O0 return x + 1; | f(int): } 1| push rbp 1| mov rbp, rsp int g( int x) { 1| mov DWORD PTR [rbp-4], edi return f(x + 2); 2| mov eax, DWORD PTR [rbp-4] } 2| add eax, 1 3| pop rbp godbolt.org/z/87GK4q 3| ret | g(int): 5| push rbp 5| mov rbp, rsp 5| sub rsp, 8 5| mov DWORD PTR [rbp-4], edi 6| mov eax, DWORD PTR [rbp-4] 6| add eax, 2 6| mov edi, eax 6| call f(int) 7| leave 7| ret godbolt.org/z/87GK4q

  45. Reading assembly for fun and profit 5 Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 1 7 6 40 / 100 4 3 2 int f( int x) { # g92 -O0 return x + 1; | f(int): } 1| push rbp 1| mov rbp, rsp int g( int x) { 1| mov DWORD PTR [rbp-4], edi return f(x + 2); 2| mov eax, DWORD PTR [rbp-4] } 2| add eax, 1 3| pop rbp godbolt.org/z/87GK4q 3| ret | g(int): 5| push rbp # g92 -O1 5| mov rbp, rsp | f(int): 5| sub rsp, 8 2| lea eax, [rdi+1] 5| mov DWORD PTR [rbp-4], edi 3| ret 6| mov eax, DWORD PTR [rbp-4] | g(int): 6| add eax, 2 2| lea eax, [rdi+3] 6| mov edi, eax 7| ret 6| call f(int) 7| leave godbolt.org/z/Yxbb6q 7| ret godbolt.org/z/87GK4q

  46. Reading assembly for fun and profit 5 Demystifying Value Categories in C++ – Reading Assembly for Fun and Profit Nis Meinert – Rostock University 1 7 6 41 / 100 3 4 2 void side_effect(); # g92 -O0 | f(int): int f( int x) { 3| push rbp auto a = x; 3| mov rbp, rsp side_effect(); 3| sub rsp, 32 return a - x; 3| mov DWORD PTR [rbp-20], edi } 4| mov eax, DWORD PTR [rbp-20] 4| mov DWORD PTR [rbp-4], eax godbolt.org/z/5xq5n5 5| call side_effect() 6| mov eax, DWORD PTR [rbp-4] 6| sub eax, DWORD PTR [rbp-20] 7| leave 7| ret godbolt.org/z/5xq5n5

  47. 42 / 100 1 Nis Meinert – Rostock University 2 3 7 4 6 5 5 6 4 7 3 2 1 Implicit costs of using const& void side_effect(); void side_effect(); int f( int x) { int f( const int & x) { auto a = x; auto a = x; side_effect(); side_effect(); return a - x; return a - x; } } godbolt.org/z/5xq5n5 godbolt.org/z/333ME7 Demystifying Value Categories in C++ – Implicit costs of const&

  48. 43 / 100 Nis Meinert – Rostock University Implicit costs of using const& # g92 -O0 # g92 -O0 | f(int): | f(int const&): 3| push rbp 3| push rbp 3| mov rbp, rsp 3| mov rbp, rsp 3| sub rsp, 32 3| sub rsp, 32 3| mov DWORD PTR [rbp-20], edi 3| mov QWORD PTR [rbp-24], rdi 4| mov eax, DWORD PTR [rbp-20] 4| mov rax, QWORD PTR [rbp-24] 4| mov DWORD PTR [rbp-4], eax 4| mov eax, DWORD PTR [rax] 5| call side_effect() 4| mov DWORD PTR [rbp-4], eax 6| mov eax, DWORD PTR [rbp-4] 5| call side_effect() 6| sub eax, DWORD PTR [rbp-20] 6| mov rax, QWORD PTR [rbp-24] 7| leave 6| mov eax, DWORD PTR [rax] 7| ret 6| mov edx, DWORD PTR [rbp-4] 6| sub edx, eax godbolt.org/z/5xq5n5 6| mov eax, edx 7| leave 7| ret godbolt.org/z/333ME7 Demystifying Value Categories in C++ – Implicit costs of const&

  49. 44 / 100 necessary when function is not a leaf function Nis Meinert – Rostock University ommitted in leaf functions.) since callee have to know where to start saving Implicit costs of using const& # g92 -O3 # g92 -O3 | f(int): | f(int const&): 3| sub rsp, 8 3| push rbp 5| call side_effect() 3| push rbx 7| xor eax, eax 3| mov rbx, rdi 7| add rsp, 8 3| sub rsp, 8 7| ret 4| mov ebp, DWORD PTR [rdi] 5| call side_effect() godbolt.org/z/od8v6e 6| mov eax, ebp 6| sub eax, DWORD PTR [rbx] NB #1: adjusting rsp in function prologue 7| add rsp, 8 7| pop rbx 7| pop rbp 7| ret variables on stack. (Adjusting rsp can be godbolt.org/z/cr8f9b Demystifying Value Categories in C++ – Implicit costs of const&

  50. 44 / 100 optimizations such as alignment: ABI requires Nis Meinert – Rostock University stack to be aligned to 16 bytes. Implicit costs of using const& # g92 -O3 # g92 -O3 | f(int): | f(int const&): 3| sub rsp, 8 3| push rbp 5| call side_effect() 3| push rbx 7| xor eax, eax 3| mov rbx, rdi 7| add rsp, 8 3| sub rsp, 8 7| ret 4| mov ebp, DWORD PTR [rdi] 5| call side_effect() godbolt.org/z/od8v6e 6| mov eax, ebp 6| sub eax, DWORD PTR [rbx] NB #2: Ofgset x in sub rsp, x is objective of 7| add rsp, 8 7| pop rbx 7| pop rbp 7| ret godbolt.org/z/cr8f9b Demystifying Value Categories in C++ – Implicit costs of const&

  51. 45 / 100 8 Nis Meinert – Rostock University 2 Even though we only pass a reference, we pay the cost of the complex object 3 4 1 5 10 6 9 7 Implicit costs of using const& #include <string> # clang900 -O3 -std=c++2a -stdlib=libc++ #include <string_view> | get_size(std::string const&): xx| movzx eax, byte ptr [rdi] auto get_size( const std::string& s) { xx| test al, 1 return s.size(); xx| je .LBB0_1 } 0| mov rax, qword ptr [rdi + 8] 5| ret auto get_size(std::string_view sv) { | .LBB0_1: return sv.size(); 0| shr rax } 5| ret | get_size(std::string_view): godbolt.org/z/Yc1hrj 8| mov rax, rsi 9| ret | godbolt.org/z/Yc1hrj std::string (i.e., first bit is tested for short string optimization) → prefer views such as std::string_view or std::span ֒ Demystifying Value Categories in C++ – Implicit costs of const&

  52. 45 / 100 7 Nis Meinert – Rostock University 2 1 3 4 10 5 9 6 8 Implicit costs of using const& #include <string> # clang900 -O3 -std=c++2a -stdlib=libc++ #include <string_view> | get_size(std::string const&): xx| movzx eax, byte ptr [rdi] auto get_size( const std::string& s) { xx| test al, 1 return s.size(); xx| je .LBB0_1 } 0| mov rax, qword ptr [rdi + 8] 5| ret auto get_size(std::string_view sv) { | .LBB0_1: return sv.size(); 0| shr rax } 5| ret | get_size(std::string_view): godbolt.org/z/Yc1hrj 8| mov rax, rsi 9| ret | godbolt.org/z/Yc1hrj ⋆ Confession: switching to libstdc++ resolves this issue here Demystifying Value Categories in C++ – Implicit costs of const&

  53. Will it compile? 6 Nis Meinert – Rostock University 2 1 9 8 1 7 46 / 100 5 4 3 2 #include <array> #include <span> int main() { constexpr std::array x{ 4, 8, 15, 16, 23, 42 }; constexpr std::span x_view{x}; } godbolt.org/z/66exs6 template < typename T, std::size_t N> constexpr span( const std::array<T, N>& arr) noexcept ; cppreference.com/w/cpp/container/span/span Demystifying Value Categories in C++ – Implicit costs of const&

  54. Will it compile? 8 Nis Meinert – Rostock University 2 1 → Solutions? expressions! objects are not constant → References to automatic storage No! 1 9 7 5 2 3 4 46 / 100 6 #include <array> #include <span> int main() { → Constructor takes by reference constexpr std::array x{ 4, 8, 15, 16, 23, 42 }; constexpr std::span x_view{x}; } godbolt.org/z/66exs6 template < typename T, std::size_t N> constexpr span( const std::array<T, N>& arr) noexcept ; cppreference.com/w/cpp/container/span/span Demystifying Value Categories in C++ – Implicit costs of const&

  55. Will it compile? 6 Nis Meinert – Rostock University 9 8 7 1 47 / 100 5 4 3 2 #include <array> #include <span> int main() { constexpr static std::array x{ 4, 8, 15, 16, 23, 42 }; constexpr std::span x_view{x}; } godbolt.org/z/Ga5Ysv Demystifying Value Categories in C++ – Implicit costs of const&

  56. Nota bene … 6 Nis Meinert – Rostock University 12 11 10 9 8 this will work though, since reference / pointer does not escape constant expression … 7 48 / 100 5 4 1 3 2 #include <array> #include <span> constexpr auto f() { std::array x{4, 8, 15, 16, 23, 42}; std::span x_view{x}; return 0; } int main() { static_assert(f() == 0); } godbolt.org/z/rso3na Demystifying Value Categories in C++ – Implicit costs of const&

  57. Time to grab some covfefe

  58. A Short Quiz for the Break 5 Nis Meinert – Rostock University 9 8 7 1 6 3 4 2 48 / 100 #!/usr/bin/env python3 def f(x): if x + 1 is 1 + x: return False if x + 2 is not 2 + x: return False return True Find all x for which f(x) returns True ! Demystifying Value Categories in C++ – Implicit costs of const&

  59. A Short Quiz for the Break 6 Nis Meinert – Rostock University 9 8 7 1 48 / 100 5 4 3 2 #!/usr/bin/env python3 def f(x): if x + 1 is 1 + x: return False if x + 2 is not 2 + x: return False return True Find all x for which f(x) returns True ! Answer: f(x=-7) Demystifying Value Categories in C++ – Implicit costs of const&

  60. PART II

  61. Dangling References

  62. Will it compile? 7 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University 13 12 11 10 9 1 8 49 / 100 3 2 5 4 6 struct S { int x; }; auto f() { S s{.x = 42}; return s; } int main() { S& s = f(); return s.x; } godbolt.org/z/x4rWKj

  63. Will it compile? 8 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University error: cannot bind non-const lvalue reference of type “S&” to an rvalue of type “S” 13 12 11 10 9 1 49 / 100 7 3 6 2 5 4 struct S { int x; }; auto f() { S s{.x = 42}; return s; } int main() { S& s = f(); return s.x; } godbolt.org/z/x4rWKj

  64. Will it invoke undefined behavior? 8 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University …binding a reference to a temporary??? 13 12 11 10 9 1 50 / 100 7 3 6 2 5 4 struct S { int x; }; auto f() { S s{.x = 42}; return s; } int main() { const S& s = f(); return s.x; } godbolt.org/z/avGMPa

  65. Temporary object lifetime extension 8 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University binding to a const lvalue reference or to an rvalue reference (since C++11).” 13 12 11 10 9 1 51 / 100 7 3 6 2 5 4 struct S { int x; }; auto f() { S s{.x = 42}; return s; } int main() { const S& s = f(); return s.x; } godbolt.org/z/avGMPa cppreference.com : “The lifetime of a temporary object may be extended by

  66. Q: What is the output of the program? 10 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University 20 19 18 17 16 15 14 13 12 1 11 52 / 100 9 6 2 3 4 5 7 8 #include <iostream> template < char id> struct Log { Log() { std::cout << id << 1; } virtual ~Log() { std::cout << id << 2; } }; struct A: Log<'a'> { int x; A( int x): x(x) {}; }; struct B: Log<'b'> { const A& a; B( const A& a): a(a) {} }; int main() { const B& b = B{A{42}}; std::cout << 'x'; return b.a.x; } godbolt.org/z/hcods4

  67. 53 / 100 20 1 14 15 16 17 18 19 Dangling reference!!! 11 → lifetime extension only for result of the temporary expression, not any sub-expression → use address sanitizer! Nis Meinert – Rostock University Demystifying Value Categories in C++ – Dangling References 12 13 6 3 5 7 4 8 9 2 10 A: a1b1a2xb2 #include <iostream> template < char id> struct Log { Log() { std::cout << id << 1; } virtual ~Log() { std::cout << id << 2; } }; struct A: Log<'a'> { int x; A( int x): x(x) {}; }; struct B: Log<'b'> { const A& a; B( const A& a): a(a) {} }; int main() { const B& b = B{A{42}}; std::cout << 'x'; return b.a.x; } godbolt.org/z/hcods4

  68. contrived?

  69. Reference lifetime extension 1 2 3 4 Nis Meinert – Rostock University Demystifying Value Categories in C++ – Dangling References 54 / 100 (derived from abseil.io : Tip of the Week #107: “Reference Lifetime Extension”) std::vector<std::string_view> explode( const std::string& s); for (std::string_view s: explode(str_cat("oo", "ps"))) { // WRONG! [...]

  70. Q: What is the output of the program? 6 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University 9 8 7 1 55 / 100 5 4 3 2 #include <vector> int main() { std::vector< int > v; v.push_back(1); auto & x = v[0]; v.push_back(2); return x; } godbolt.org/z/M6bx1Y

  71. Q: What is the output of the program? 7 Demystifying Value Categories in C++ – Dangling References Nis Meinert – Rostock University → use address sanitizer! is pushed the space the second time an element Dangling reference!!! 9 8 1 55 / 100 6 5 4 3 2 #include <vector> int main() { std::vector< int > v; → std::vector needs to reallocate all v.push_back(1); auto & x = v[0]; v.push_back(2); return x; } godbolt.org/z/M6bx1Y

  72. std::move in the wild

  73. 56 / 100 8 Nis Meinert – Rostock University 9 8 7 6 5 4 3 2 (derived from CppCon 2019: Ben Deane “Everyday Efgiciency: In-Place Construction (Back to Basics?)”) 9 1 7 5 1 2 6 3 4 Moving std::string static void cp_small_str(benchmark::State& state) { for ( auto _ : state) { std::string original("small"); benchmark::DoNotOptimize(original); std::string copied = original; benchmark::DoNotOptimize(copied); } } BENCHMARK(cp_small_str); static void mv_small_str(benchmark::State& state) { for ( auto _ : state) { std::string original("small"); benchmark::DoNotOptimize(original); std::string moved = std::move(original); benchmark::DoNotOptimize(moved); } } BENCHMARK(mv_small_str); Demystifying Value Categories in C++ – std::move in the wild

  74. 57 / 100 8 Nis Meinert – Rostock University 9 8 7 6 5 4 3 2 (derived from CppCon 2019: Ben Deane “Everyday Efgiciency: In-Place Construction (Back to Basics?)”) 9 1 7 5 1 2 6 3 4 Moving std::string static void cp_long_str(benchmark::State& state) { for ( auto _ : state) { std::string original("this is too long for short string optimization"); benchmark::DoNotOptimize(original); std::string copied = original; benchmark::DoNotOptimize(copied); } } BENCHMARK(cp_long_str); static void mv_long_str(benchmark::State& state) { for ( auto _ : state) { std::string original("this is too long for short string optimization"); benchmark::DoNotOptimize(original); std::string moved = std::move(original); benchmark::DoNotOptimize(moved); } } BENCHMARK(mv_long_str); Demystifying Value Categories in C++ – std::move in the wild

  75. Quick Bench result Nis Meinert – Rostock University 58 / 100 Moving std::string Quick Bench: tinyurl.com/yybmdngv Demystifying Value Categories in C++ – std::move in the wild

  76. 1. copy stack allocated data 1. copy stack allocated data 2. set string length of moved string to zero Nis Meinert – Rostock University 59 / 100 Moving std::string Copy small std::string Move small std::string → moving is not necessarily better than copying! ֒ Demystifying Value Categories in C++ – std::move in the wild

  77. 60 / 100 sentinel node, because moved from Nis Meinert – Rostock University need to allocate → Move assignment can swap, thus no (albeit in an unspecified state) container must still be a valid container → Move ctor needs to allocate new Moving std::map Did they forget to mark the move ctor noexcept ? // since C++11 std::map( const std::map&&) // until C++17 std::map& operator =(std::map&&) // since C++17 std::map& operator =(std::map&&) noexcept Demystifying Value Categories in C++ – std::move in the wild

  78. 60 / 100 → Move ctor needs to allocate new Nis Meinert – Rostock University need to allocate → Move assignment can swap, thus no (albeit in an unspecified state) sentinel node, because moved from container must still be a valid container Moving std::map Did they forget to mark the move ctor noexcept ? No! // since C++11 std::map( const std::map&&) // until C++17 std::map& operator =(std::map&&) // since C++17 std::map& operator =(std::map&&) noexcept → move ctor of std::map allocates heap space! ֒ (Billy O’Neal: twitter.com/MalwareMinigun/status/1165310509022736384 ) Demystifying Value Categories in C++ – std::move in the wild

  79. 61 / 100 6 2 10 3 9 4 8 5 7 6 1 7 5 8 4 9 3 10 2 Nis Meinert – Rostock University 1 Moving std::map static void no_move(benchmark::State& state) { for ( auto _ : state) { auto m = []() -> std::map< int , int > { std::map< int , int > m{{0, 42}}; return m; }(); benchmark::DoNotOptimize(m); } } BENCHMARK(no_move); static void force_move(benchmark::State& state) { for ( auto _ : state) { auto m = []() -> std::map< int , int > { std::map< int , int > m{{0, 42}}; return std::move(m); }(); benchmark::DoNotOptimize(m); } } BENCHMARK(force_move); Demystifying Value Categories in C++ – std::move in the wild

  80. 62 / 100 5 Nis Meinert – Rostock University 9 8 7 1 6 4 3 2 Moving std::map static void copy(benchmark::State& state) { for ( auto _ : state) { std::map< int , int > m{{0, 42}}; benchmark::DoNotOptimize(m); auto m2 = m; benchmark::DoNotOptimize(m2); } } BENCHMARK(copy); Demystifying Value Categories in C++ – std::move in the wild

  81. Quick Bench result Nis Meinert – Rostock University 63 / 100 Moving std::map Quick Bench: tinyurl.com/y57egvjp Demystifying Value Categories in C++ – std::move in the wild

Recommend


More recommend