Upgrade to Pro — share decks privately, control downloads, hide ads and more …

StockholmCpp23 - Variant Visitation Variations

StockholmCpp23 - Variant Visitation Variations

std::variant<> and std::visit() are arguably among the most important new features of C++17, allowing for the type safe flexibility provided by virtual functions, without the coupling from inheritance. But some say visit() is slow. Let's find out, and try a few different techniques to implement visit() and see what the standard says.

5d138ccf47c8d79aa8f0d29900f9e07b?s=128

Björn Fahller

June 13, 2019
Tweet

Transcript

  1. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    1/50 Variant Visitation Variations Björn Fahller
  2. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    2/50 <variant> std::variant<Ts...>
  3. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    3/50 <variant> std::variant<Ts...> – In C++ 17
  4. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    4/50 <variant> std::variant<Ts...> – In C++ 17 – Is a sum-type
  5. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    5/50 <variant> std::variant<Ts...> – In C++ 17 – Is a sum-type using V = std::variant<A,B>; Instances of V can hold either the values from A, or the values from B. i.e., the value space of V is the sum of the value spaces of A and B.
  6. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    6/50 <variant> std::variant<Ts...> – In C++ 17 – Is a sum-type – Is type safe
  7. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    7/50 <variant> std::variant<Ts...> – In C++ 17 – Is a sum-type – Is type safe using V = std::variant<A,B>; V v{A{}};
  8. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    8/50 <variant> std::variant<Ts...> – In C++ 17 – Is a sum-type – Is type safe using V = std::variant<A,B>; V v{A{}}; if (std::holds_alternative<A>(v)) func(std::get<A>(v));
  9. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    9/50 <variant> std::variant<Ts...> – In C++ 17 – Is a sum-type – Is type safe using V = std::variant<A,B>; V v{A{}}; if (std::holds_alternative<A>(v)) func(std::get<A>(v)); std::get<B>(v); // throws
  10. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    10/50 <variant> std::variant<Ts...> – In C++ 17 – Is a sum-type – Is type safe – Is a value type
  11. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    11/50 <variant> auto std::visit(visitor, variants...) – Calls visitor with the actual types held by the variants – visitor must have overloads that spans the entire type space
  12. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    12/50 <variant> auto std::visit(visitor, variants...) – Calls visitor with the actual types held by the variants – visitor must have overloads that spans the entire type space struct add {}; struct subtract {}; struct constant { int value; }; struct variable { std::string name; }; using expression = std::variant< add, subtract, constant, variable>; expression evaluate(expression e) { struct visitor { expression operator()(add) { … } expression operator()(subtract) { … } expression operator()(constant c) { … c.value; } expression operator()(variable v) { … lookup(v.name); } }; return std::visit(visitor{}, e); }
  13. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    13/50 <variant> auto std::visit(visitor, variants...) – Calls visitor with the actual types held by the variants – visitor must have overloads that spans the entire type space struct add {}; struct subtract {}; struct constant { int value; }; struct variable { std::string name; }; using expression = std::variant< add, subtract, constant, variable>; expression evaluate(expression e) { struct visitor { expression operator()(add) { … } expression operator()(subtract) { … } expression operator()(constant c) { … c.value; } expression operator()(variable v) { … lookup(v.name); } }; return std::visit(visitor{}, e); } Note that the types in the variant are only conceptually related, they are not related in the type system.
  14. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    14/50 <variant> auto std::visit(visitor, variants...) – Calls visitor with the actual types held by the variants – visitor must have overloads that spans the entire type space
  15. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    15/50 <variant> Mateusz Pusz ACCU 2019 20 minutes https://www.youtube.com/watch?v=JGYxOieiZnY
  16. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    16/50 <variant> Some say generated code is inefficient
  17. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    17/50 <variant> Some say generated code is inefficient – Let’s have a look
  18. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    18/50 <variant> Some say generated code is inefficient – Let’s have a look https://gcc.godbolt.org/z/51ufc6
  19. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    19/50 <variant> Some say generated code is inefficient – Let’s have a look – Why the check for index==-1? https://gcc.godbolt.org/z/51ufc6
  20. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    20/50 Throws: bad_­ variant_­ access if any variant in vars is valueless_­ by_­ exception(). http://eel.is/c++draft/variant.visit#4
  21. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    21/50 http://eel.is/c++draft/variant.variant#variant.status 20.7.3.5 Value status [variant.status] constexpr bool valueless_by_exception() const noexcept; Effects: Returns false if and only if the variant holds a value. [ Note: A variant might not hold a value if an exception is thrown during a type-changing assignment or emplacement. The latter means that even a variant<float, int> can become valueless_­ by_­ exception(), for instance by struct S { operator int() { throw 42; }}; variant<float, int> v{12.f}; v.emplace<1>(S()); — end note ] Throws: bad_­ variant_­ access if any variant in vars is valueless_­ by_­ exception(). http://eel.is/c++draft/variant.visit#4
  22. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    22/50 http://eel.is/c++draft/variant.variant#variant.status 20.7.3.5 Value status [variant.status] constexpr bool valueless_by_exception() const noexcept; Effects: Returns false if and only if the variant holds a value. [ Note: A variant might not hold a value if an exception is thrown during a type-changing assignment or emplacement. The latter means that even a variant<float, int> can become valueless_­ by_­ exception(), for instance by struct S { operator int() { throw 42; }}; variant<float, int> v{12.f}; v.emplace<1>(S()); — end note ] Throws: bad_­ variant_­ access if any variant in vars is valueless_­ by_­ exception(). http://eel.is/c++draft/variant.visit#4
  23. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    23/50 http://eel.is/c++draft/variant.variant#variant.status 20.7.3.5 Value status [variant.status] constexpr bool valueless_by_exception() const noexcept; Effects: Returns false if and only if the variant holds a value. [ Note: A variant might not hold a value if an exception is thrown during a type-changing assignment or emplacement. The latter means that even a variant<float, int> can become valueless_­ by_­ exception(), for instance by struct S { operator int() { throw 42; }}; variant<float, int> v{12.f}; v.emplace<1>(S()); — end note ] Throws: bad_­ variant_­ access if any variant in vars is valueless_­ by_­ exception(). http://eel.is/c++draft/variant.visit#4
  24. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    24/50 http://eel.is/c++draft/variant.variant#variant.status 20.7.3.5 Value status [variant.status] constexpr bool valueless_by_exception() const noexcept; Effects: Returns false if and only if the variant holds a value. [ Note: A variant might not hold a value if an exception is thrown during a type-changing assignment or emplacement. The latter means that even a variant<float, int> can become valueless_­ by_­ exception(), for instance by struct S { operator int() { throw 42; }}; variant<float, int> v{12.f}; v.emplace<1>(S()); — end note ] Throws: bad_­ variant_­ access if any variant in vars is valueless_­ by_­ exception(). http://eel.is/c++draft/variant.visit#4
  25. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    25/50 http://eel.is/c++draft/variant.variant#variant.status 20.7.3.5 Value status [variant.status] constexpr bool valueless_by_exception() const noexcept; Effects: Returns false if and only if the variant holds a value. [ Note: A variant might not hold a value if an exception is thrown during a type-changing assignment or emplacement. The latter means that even a variant<float, int> can become valueless_­ by_­ exception(), for instance by struct S { operator int() { throw 42; }}; variant<float, int> v{12.f}; v.emplace<1>(S()); — end note ] Throws: bad_­ variant_­ access if any variant in vars is valueless_­ by_­ exception(). http://eel.is/c++draft/variant.visit#4
  26. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    26/50 <variant> Some say generated code is inefficient – Let’s have a look – Why the check for index==-1? – Why calls through function pointers? https://gcc.godbolt.org/z/51ufc6
  27. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    27/50 http://eel.is/c++draft/variant.visit#5 template<class Visitor, class... Variants> constexpr see below visit(Visitor&& vis, Variants&&... vars); 1. Let n be sizeof...(Variants)… … 5. Complexity: For n ≤ 1, the invocation of the callable object is implemented in constant time, i.e., for n = 1, it does not depend on the number of alternative types of Variants 0 . For n > 1, the invocation of the callable object has no complexity requirements.
  28. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    28/50 http://eel.is/c++draft/variant.visit#5 template<class Visitor, class... Variants> constexpr see below visit(Visitor&& vis, Variants&&... vars); 1. Let n be sizeof...(Variants)… … 5. Complexity: For n ≤ 1, the invocation of the callable object is implemented in constant time, i.e., for n = 1, it does not depend on the number of alternative types of Variants 0 . For n > 1, the invocation of the callable object has no complexity requirements.
  29. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    29/50 http://eel.is/c++draft/variant.visit#5 template<class Visitor, class... Variants> constexpr see below visit(Visitor&& vis, Variants&&... vars); 1. Let n be sizeof...(Variants)… … 5. Complexity: For n ≤ 1, the invocation of the callable object is implemented in constant time, i.e., for n = 1, it does not depend on the number of alternative types of Variants 0 . For n > 1, the invocation of the callable object has no complexity requirements.
  30. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    30/50 <variant> Some say generated code is inefficient – Let’s have a look – Why the check for index==-1? – Why calls through function pointers? – Let’s break the rules! https://gcc.godbolt.org/z/51ufc6
  31. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    31/50 A non-conforming visit() template < typename Vi, typename Va, size_t idx = std::variant_size_v<std::remove_reference_t<Va>> - 1 > auto visit(Vi&& vi, Va&& va) { if (idx == va.index()) { return std::invoke( std::forward<Vi>(vi), std::get<idx>(std::forward<Va>(va)) ); } if constexpr (idx > 0) { return visit<Vi, Va, idx - 1>( std::forward<Vi>(vi), std::forward<Va>(va) ); } else throw std::bad_variant_access(); }
  32. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    32/50 template < typename Vi, typename Va, size_t idx = std::variant_size_v<std::remove_reference_t<Va>> - 1 > auto visit(Vi&& vi, Va&& va) { if (idx == va.index()) { return std::invoke( std::forward<Vi>(vi), std::get<idx>(std::forward<Va>(va)) ); } if constexpr (idx > 0) { return visit<Vi, Va, idx - 1>( std::forward<Vi>(vi), std::forward<Va>(va) ); } else throw std::bad_variant_access(); }
  33. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    33/50 template < typename Vi, typename Va, size_t idx = std::variant_size_v<std::remove_reference_t<Va>> - 1 > auto visit(Vi&& vi, Va&& va) { if (idx == va.index()) { return std::invoke( std::forward<Vi>(vi), std::get<idx>(std::forward<Va>(va)) ); } if constexpr (idx > 0) { return visit<Vi, Va, idx - 1>( std::forward<Vi>(vi), std::forward<Va>(va) ); } else throw std::bad_variant_access(); }
  34. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    34/50 template < typename Vi, typename Va, size_t idx = std::variant_size_v<std::remove_reference_t<Va>> - 1 > auto visit(Vi&& vi, Va&& va) { if (idx == va.index()) { return std::invoke( std::forward<Vi>(vi), std::get<idx>(std::forward<Va>(va)) ); } if constexpr (idx > 0) { return visit<Vi, Va, idx - 1>( std::forward<Vi>(vi), std::forward<Va>(va) ); } else throw std::bad_variant_access(); }
  35. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    35/50 template < typename Vi, typename Va, size_t idx = std::variant_size_v<std::remove_reference_t<Va>> - 1 > auto visit(Vi&& vi, Va&& va) { if (idx == va.index()) { return std::invoke( std::forward<Vi>(vi), std::get<idx>(std::forward<Va>(va)) ); } if constexpr (idx > 0) { return visit<Vi, Va, idx - 1>( std::forward<Vi>(vi), std::forward<Va>(va) ); } else throw std::bad_variant_access(); }
  36. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    36/50 template < typename Vi, typename Va, size_t idx = std::variant_size_v<std::remove_reference_t<Va>> - 1 > auto visit(Vi&& vi, Va&& va) { if (idx == va.index()) { return std::invoke( std::forward<Vi>(vi), std::get<idx>(std::forward<Va>(va)) ); } if constexpr (idx > 0) { return visit<Vi, Va, idx - 1>( std::forward<Vi>(vi), std::forward<Va>(va) ); } else throw std::bad_variant_access(); }
  37. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    37/50 template < typename Vi, typename Va, size_t idx = std::variant_size_v<std::remove_reference_t<Va>> - 1 > auto visit(Vi&& vi, Va&& va) { if (idx == va.index()) { return std::invoke( std::forward<Vi>(vi), std::get<idx>(std::forward<Va>(va)) ); } if constexpr (idx > 0) { return visit<Vi, Va, idx - 1>( std::forward<Vi>(vi), std::forward<Va>(va) ); } else throw std::bad_variant_access(); } http://tiny.cc/sxaw7y cppinsights.io
  38. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    38/50 template < typename Vi, typename Va, size_t idx = std::variant_size_v<std::remove_reference_t<Va>> - 1 > auto visit(Vi&& vi, Va&& va) { if (idx == va.index()) { return std::invoke( std::forward<Vi>(vi), std::get<idx>(std::forward<Va>(va)) ); } if constexpr (idx > 0) { return visit<Vi, Va, idx - 1>( std::forward<Vi>(vi), std::forward<Va>(va) ); } else throw std::bad_variant_access(); } http://tiny.cc/sxaw7y cppinsights.io https://gcc.godbolt.org/z/0mtCdY
  39. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    39/50 <variant> Is visit() slow?
  40. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    40/50 <variant> Is visit() slow?
  41. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    41/50 <variant> Is visit() slow? – Not really
  42. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    42/50 <variant> Is visit() slow? – Not really, but it’s not as fast as it could be.
  43. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    43/50 <variant> Is visit() slow? – Not really, but it’s not as fast as it could be. – It’s surprisingly easy to write a fast(er) visit.
  44. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    44/50 <variant> Is visit() slow? – Not really, but it’s not as fast as it could be. – It’s surprisingly easy to write a fast(er) visit. – The performance guarantees from the standard actually hurts the performance.
  45. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    45/50 <variant> Is visit() slow? – Not really, but it’s not as fast as it could be. – It’s surprisingly easy to write a fast(er) visit. – The performance guarantees from the standard actually hurts the performance. – LLVMs if-else sequence to jump table optimization makes a non-complying implementation compliant!
  46. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    46/50 Resources
  47. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    47/50 Resources “Variant Visitation V2” - Michael Park https://mpark.github.io/programming/2019/01/22/variant-visitation-v2/
  48. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    48/50 Resources “Variant Visitation V2” - Michael Park https://mpark.github.io/programming/2019/01/22/variant-visitation-v2/ “When performance guarantees hurts performance – std::visit” - Björn Fahller https://playfulprogramming.blogspot.com/2018/12/when-performance-guarantees-hu rts.html
  49. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    49/50 Resources “Variant Visitation V2” - Michael Park https://mpark.github.io/programming/2019/01/22/variant-visitation-v2/ “When performance guarantees hurts performance – std::visit” - Björn Fahller https://playfulprogramming.blogspot.com/2018/12/when-performance-guarantees-hu rts.html “CPPInsights.io” - Andreas Fertig https://cppinsights.io
  50. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    50/50 Björn Fahller Variant Visitation Variations bjorn@fahller.se @bjorn_fahller @rollbear