Björn Fahller
June 13, 2019
120

# 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.

June 13, 2019

## 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

std::variant

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

std::variant
– In C++ 17

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

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

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.

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

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

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

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

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

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

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 subtract {};
struct constant { int value; };
struct variable { std::string name; };
using expression = std::variant<
expression evaluate(expression e)
{
struct visitor {
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

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 subtract {};
struct constant { int value; };
struct variable { std::string name; };
using expression = std::variant<
expression evaluate(expression e)
{
struct visitor {
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

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

Mateusz Pusz
ACCU 2019
20 minutes

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

Some say generated code is inefficient

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

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

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

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

20. Variant Visitation Variations – StockholmCpp 23 © Björn Fahller @bjorn_fahller 20/50
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 can become
valueless_­
by_­
exception(), for instance by
struct S { operator int() { throw 42; }};
variant v{12.f};
v.emplace<1>(S());
— end note ]
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 can become
valueless_­
by_­
exception(), for instance by
struct S { operator int() { throw 42; }};
variant v{12.f};
v.emplace<1>(S());
— end note ]
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 can become
valueless_­
by_­
exception(), for instance by
struct S { operator int() { throw 42; }};
variant v{12.f};
v.emplace<1>(S());
— end note ]
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 can become
valueless_­
by_­
exception(), for instance by
struct S { operator int() { throw 42; }};
variant v{12.f};
v.emplace<1>(S());
— end note ]
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 can become
valueless_­
by_­
exception(), for instance by
struct S { operator int() { throw 42; }};
variant v{12.f};
v.emplace<1>(S());
— end note ]
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

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
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
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
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

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

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

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

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

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

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

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

Is visit() slow?

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

Is visit() slow?

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

Is visit() slow?
– Not really

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.

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.

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.

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!

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
[email protected]
@bjorn_fahller
@rollbear