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.

Björn Fahller

June 13, 2019
Tweet

More Decks by Björn Fahller

Other Decks in Programming

Transcript

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

    4/50 <variant> std::variant<Ts...> – In C++ 17 – Is a sum-type
  2. 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.
  3. 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
  4. 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{}};
  5. 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));
  6. 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
  7. 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
  8. 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
  9. 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); }
  10. 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.
  11. 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
  12. 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
  13. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

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

    17/50 <variant> Some say generated code is inefficient – Let’s have a look
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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.
  25. 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.
  26. 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.
  27. 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
  28. 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(); }
  29. 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(); }
  30. 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(); }
  31. 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(); }
  32. 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(); }
  33. 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(); }
  34. 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
  35. 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
  36. 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.
  37. 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.
  38. 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.
  39. 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!
  40. 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/
  41. 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
  42. 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
  43. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller

    50/50 Björn Fahller Variant Visitation Variations [email protected] @bjorn_fahller @rollbear