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 1/50
    Variant Visitation Variations
    Björn Fahller

    View Slide

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

    std::variant

    View Slide

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

    std::variant
    – In C++ 17

    View Slide

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

    std::variant
    – In C++ 17
    – Is a sum-type

    View Slide

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

    std::variant
    – In C++ 17
    – Is a sum-type
    using V = std::variant;
    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.

    View Slide

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

    std::variant
    – In C++ 17
    – Is a sum-type
    – Is type safe

    View Slide

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

    std::variant
    – In C++ 17
    – Is a sum-type
    – Is type safe
    using V = std::variant;
    V v{A{}};

    View Slide

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

    std::variant
    – In C++ 17
    – Is a sum-type
    – Is type safe
    using V = std::variant;
    V v{A{}};
    if (std::holds_alternative(v))
    func(std::get(v));

    View Slide

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

    std::variant
    – In C++ 17
    – Is a sum-type
    – Is type safe
    using V = std::variant;
    V v{A{}};
    if (std::holds_alternative(v))
    func(std::get(v));
    std::get(v); // throws

    View Slide

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

    std::variant
    – In C++ 17
    – Is a sum-type
    – Is type safe
    – Is a value type

    View Slide

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

    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

    View Slide

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

    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);
    }

    View Slide

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

    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.

    View Slide

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

    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

    View Slide

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

    Mateusz Pusz
    ACCU 2019
    20 minutes
    https://www.youtube.com/watch?v=JGYxOieiZnY

    View Slide

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

    Some say generated code is inefficient

    View Slide

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

    Some say generated code is inefficient
    – Let’s have a look

    View Slide

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

    Some say generated code is inefficient
    – Let’s have a look
    https://gcc.godbolt.org/z/51ufc6

    View Slide

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

    Some say generated code is inefficient
    – Let’s have a look
    – Why the check for index==-1?
    https://gcc.godbolt.org/z/51ufc6

    View Slide

  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

    View Slide

  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 can become
    valueless_­
    by_­
    exception(), for instance by
    struct S { operator int() { throw 42; }};
    variant 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

    View Slide

  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 can become
    valueless_­
    by_­
    exception(), for instance by
    struct S { operator int() { throw 42; }};
    variant 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

    View Slide

  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 can become
    valueless_­
    by_­
    exception(), for instance by
    struct S { operator int() { throw 42; }};
    variant 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

    View Slide

  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 can become
    valueless_­
    by_­
    exception(), for instance by
    struct S { operator int() { throw 42; }};
    variant 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

    View Slide

  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 can become
    valueless_­
    by_­
    exception(), for instance by
    struct S { operator int() { throw 42; }};
    variant 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

    View Slide

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

    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

    View Slide

  27. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller 27/50
    http://eel.is/c++draft/variant.visit#5
    template
    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.

    View Slide

  28. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller 28/50
    http://eel.is/c++draft/variant.visit#5
    template
    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.

    View Slide

  29. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller 29/50
    http://eel.is/c++draft/variant.visit#5
    template
    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.

    View Slide

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

    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

    View Slide

  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> - 1
    >
    auto visit(Vi&& vi, Va&& va)
    {
    if (idx == va.index()) {
    return std::invoke( std::forward(vi),
    std::get(std::forward(va)) );
    }
    if constexpr (idx > 0) {
    return visit( std::forward(vi),
    std::forward(va) );
    }
    else throw std::bad_variant_access();
    }

    View Slide

  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> - 1
    >
    auto visit(Vi&& vi, Va&& va)
    {
    if (idx == va.index()) {
    return std::invoke( std::forward(vi),
    std::get(std::forward(va)) );
    }
    if constexpr (idx > 0) {
    return visit( std::forward(vi),
    std::forward(va) );
    }
    else throw std::bad_variant_access();
    }

    View Slide

  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> - 1
    >
    auto visit(Vi&& vi, Va&& va)
    {
    if (idx == va.index()) {
    return std::invoke( std::forward(vi),
    std::get(std::forward(va)) );
    }
    if constexpr (idx > 0) {
    return visit( std::forward(vi),
    std::forward(va) );
    }
    else throw std::bad_variant_access();
    }

    View Slide

  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> - 1
    >
    auto visit(Vi&& vi, Va&& va)
    {
    if (idx == va.index()) {
    return std::invoke( std::forward(vi),
    std::get(std::forward(va)) );
    }
    if constexpr (idx > 0) {
    return visit( std::forward(vi),
    std::forward(va) );
    }
    else throw std::bad_variant_access();
    }

    View Slide

  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> - 1
    >
    auto visit(Vi&& vi, Va&& va)
    {
    if (idx == va.index()) {
    return std::invoke( std::forward(vi),
    std::get(std::forward(va)) );
    }
    if constexpr (idx > 0) {
    return visit( std::forward(vi),
    std::forward(va) );
    }
    else throw std::bad_variant_access();
    }

    View Slide

  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> - 1
    >
    auto visit(Vi&& vi, Va&& va)
    {
    if (idx == va.index()) {
    return std::invoke( std::forward(vi),
    std::get(std::forward(va)) );
    }
    if constexpr (idx > 0) {
    return visit( std::forward(vi),
    std::forward(va) );
    }
    else throw std::bad_variant_access();
    }

    View Slide

  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> - 1
    >
    auto visit(Vi&& vi, Va&& va)
    {
    if (idx == va.index()) {
    return std::invoke( std::forward(vi),
    std::get(std::forward(va)) );
    }
    if constexpr (idx > 0) {
    return visit( std::forward(vi),
    std::forward(va) );
    }
    else throw std::bad_variant_access();
    }
    http://tiny.cc/sxaw7y
    cppinsights.io

    View Slide

  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> - 1
    >
    auto visit(Vi&& vi, Va&& va)
    {
    if (idx == va.index()) {
    return std::invoke( std::forward(vi),
    std::get(std::forward(va)) );
    }
    if constexpr (idx > 0) {
    return visit( std::forward(vi),
    std::forward(va) );
    }
    else throw std::bad_variant_access();
    }
    http://tiny.cc/sxaw7y
    cppinsights.io
    https://gcc.godbolt.org/z/0mtCdY

    View Slide

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

    Is visit() slow?

    View Slide

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

    Is visit() slow?

    View Slide

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

    Is visit() slow?
    – Not really

    View Slide

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

    Is visit() slow?
    – Not really, but it’s not as fast as it could be.

    View Slide

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

    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.

    View Slide

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

    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.

    View Slide

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

    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!

    View Slide

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

    View Slide

  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/

    View Slide

  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

    View Slide

  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

    View Slide

  50. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller 50/50
    Björn Fahller
    Variant Visitation Variations
    [email protected]
    @bjorn_fahller
    @rollbear

    View Slide