While discussing upcoming C++17 features with other attendees at CppCon 2016, I was surprised to hear complaints about the fact that std::variant visitation requires an external callable object.

Even though std::visit requires an overloaded callable object as its first argument, it is possible to build such an object locally in the call site: this can easily be achieved by implementing something similar to std::overload , proposed in P0051R2.

The aforementioned task however becomes trivial when using boost::hana .

In this article, we're gonna take a look at:

"Traditional" variant visitation.

"Lambda-based" variant visitation using boost::hana .

"Fire-and-forget" variant visitation.

Future improvements/considerations.

boost::variant or std::variant ?

Before looking at visitation techniques, I just wanted to mention that everything written in this article applies both to boost::variant and std::variant .

In fact, I've written a very simple wrapper for the upcoming examples that conditionally aliases vr::variant<Ts...> to std::variant<Ts...> if available, otherwise to boost::variant<Ts...> .

The vr::visit(xs...) function is similarly an alias for std::visit(xs...) if available, otherwise for boost::apply_visitor(xs...) .

All the code snippets in this article are therefore compliant to the C++14 standard.

"Traditional" visitation

Visiting a variant is usually done by writing a visitor struct / class outside of the scope where the variant is actually being visited. The visitor contains a set of overloads that will match the types a variant can hold.

As an example, let's say that we have a variant type alias vnum that can hold one of several numerical types:

// Can hold either an `int`, a `float` or a `double`. using vnum = vr::variant< int , float , double >;

We want to to visit this variant and print...

"$i" for integers.

"$f" for floats.

"$d" for doubles.

...where $ is the currently stored value.

To achieve that, it's sufficient to write a vnum_printer struct with an operator() overload for every type supported by our variant:

struct vnum_printer { void operator ()( int x) { cout << x << "i

" ; } void operator ()( float x) { cout << x << "f

" ; } void operator ()( double x) { cout << x << "d

" ; } };

Afterwards, we can simply invoke vr::visit using an instance of vnum_printer as the first argument, and an instance of the vnum variant as the second one:

// Prints "0i". vnum v0{ 0 }; vr::visit(vnum_printer{}, v0); // Prints "5f". v0 = 5.f ; vr::visit(vnum_printer{}, v0); // Prints "33.51d". v0 = 33.51 ; vr::visit(vnum_printer{}, v0);

This works, but requires us to define a new vnum_printer visitor type - this boilerplate can be avoided.

(You can find a similar example on GitHub.)

How does vr::visit work?

You might be asking...

How does vr::visit work?

The idea is very simple: by providing an overloaded callable object to vr::visit , it can internally do something like this:

// Warning: PSEUDOCODE! // The code below is just an example for `vnum`. using vnum = vr::variant< int , float , double >; template < typename TVisitor> auto visit(TVisitor visitor, vnum variant) { // Everything in here would be generated by the compiler... if constexpr (variant.index() == 0 ) { return visitor( std:: get< int >(variant)); } else if constexpr (variant.index() == 1 ) { return visitor( std:: get< float >(variant)); } else if constexpr (variant.index() == 2 ) { return visitor( std:: get< double >(variant)); } else { static_assert ( false , "Invalid variant state." ); } }

As you can see from the pseudocode above, all we need is a way of generating a set of overloads in order to build a valid visitor.

"Lambda-based" visitation

So, our task is to create an overload set from a variadic number of lambdas.

How can we do that?

We need a variadic make_overload(...) template function that takes any number of callable objects as input and returns a single callable object as its output.

The returned callable object will simply be an overload of all the input callable objects. Example:

auto x = make_overload ( []( int y){ cout << "int!

" ; }, []( float y){ cout << "float!

" ; } ); // Prints "int!". x( 0 ); // Prints "float!". x( 0.f );

Covering the implementation of make_overload is out of the scope of this article (but an interesting idea for a future one). Therefore, you have a few options:

Implement your own make_overload(...) function using online resources (e.g. the std::overload proposal). Copy-paste my vrm::core::make_overload(...) implementation. Use boost::hana::overload , a well-tested production-ready solution. This is the approach we're going to use for the article.

Once you get hold of a C++14-compliant compiler and #include <boost/hana.hpp> ... you're pretty much done!

An overloaded callable object supporting a variant's types is a valid visitor. Here's a minimal example:

auto my_visitor = boost:: hana :: overload ( []( int ){ std:: cout << "int!

" ; }, []( float ){ std:: cout << "float!

" ; } ); vr::variant< int , float > my_variant{ 0 }; // Prints "int!". vr::visit(my_visitor, my_variant); // Prints "float!". my_variant = 5.f ; vr::visit(my_visitor, my_variant);

(You can find a similar example on GitHub.)

"Fire-and-forget" visitation

Sometimes you might want to visit a single variant with a "fire-and-forget" anonymous overload set of lambdas. That's very easy to implement, as well. We'll create a function that takes a variant as its first argument, then any number of callable objects:

template < typename TVariant, typename ... TVisitors> auto visit_in_place(TVariant&& variant, TVisitors&&... visitors) { return vr::visit ( boost:: hana :: overload( std:: forward<TVisitors>(visitors)...), std:: forward<TVariant>(variant) ); }

(Note: std::forward needs to be used in order to perfectly-forward variant and visitors , which are "forwarding references").

visit_in_place can be used as follows:

struct response_success { payload _p; message _m; }; struct response_failure { message _m; }; // ... using http_response = vr::variant<response_success, response_failure>; http_response send_http_request( /* ... */ ); // ... visit_in_place ( send_http_request( "get_users" , some_endpoint), []( const response_success& x) { cout << "Successfully received response:

" << " \t Message: " << x._m << "

" ; update_user_list(x._p.as< std:: vector<User>>()); }, []( const response_failure& x) { cerr << "Request failure:

" << " \t Error: " << x._m << "

" ; } );

As you can see from the example, visit_in_place is useful for very specific variant visits, where the logic is not reused and all boilerplate can be easily avoided.

Final thoughts, future improvements

"Lambda-based" visitation is a very elegant way of visiting variants with minimal boilerplate. "Fire-and-forget" visitation is even better when dealing with one-time specific variant visits.

Both of them, however, do not play nicely when a stateful visitor is required. In that case, writing a struct / class is probably the cleanest option. Making the visitor a local type or hiding it behind an implementation namespace is a good idea to keep it as local as possible.

Throughout the article we also did not cover recursive variant visitation: that will be the topic for my next article. It is possible to build a "lambda-based" and "fire-and-forget" visitation function for recursive variants as well, avoiding any extra overhead (e.g. std::function is not going to be used).

Something else that could be interesting to explore is linear overloading, provided by boost::hana::overload_linearly . This kind of overloading calls the first matching function in linear order - using this alongside SFINAE and generic lambdas could allow the creation of a visitor that uses a single function for a group of types sharing a common interface, or of a visitor that has a "default" case at the end (useful when you want to ignore some types). That will also be covered in a future article.

Thanks for reading!

series