boost::variant and a general, generic visitor class
published at 25.07.2015 12:35 by Jens Weller
Save to Instapaper Pocket
So, I started a new project, and I do use boost::variant to be able to stick otherwise unrelated classes into the same container. Actually a tree, but that doesn't matter here. With boost::variant, you simply derive your visitor class from the static_visitor class, which lets you visit the types in a boost::variant via the call operator. When you want to do always the same for all types, you simply can add a template method version of the call operator. This way, you can easily write a variant to a stream or print its actual type id via typeid(T).hash_code();.
One need I have for my interface, is returning a name for a node in my tree, to be displayed later in Qt, I use this name visitor for this:
struct NameVisitor : boost::static_visitor<std::string> { template<class T> std::string operator()(const T& t)const { return t.getName(); } };
This implies, that all types in the variant have a getName method, yet I don't have to derive my classes from a common interface. If inheritance would be a better here, is a different discussion... For now, thats what I'd like to go with. But, as my interface evolves, e.g. I already have an IdVisitor calling getId, only differing in the name and calling getId plus returning int instead of std::string. Everything else, the same. So, what if there is a generic solution, and I don't have to write new visitors for each new method in my interface?
I have discussed this topic intensively with my twitter followers at @meetingcpp, but already explaining the problem in 140 characters is challenging, yet the discussion has shown a few interesting ways to come close to my goal... I want to thank Patrice Roy, Andrey Upadyshev, tweetsbi, MJKlaim and a few others for their inputs. Patrice Roy has even blogged about it (in french), mainly how a C++11 solution could look like...
The static_visitor I have to derive from, gives a very clear interface for the template method: it has a single argument, this also prevents us from adding generic visitors as local classes: they cannot contain template methods. A generic solution should turn the visitor class in a template class, which derives from static_visitor<ReturnTypeOfMethodInterface>, also we will need some generic way of saying call method x on generic type T, which is later only known to the template method actually doing the call to the member function. If that method does not exist, well, thats also a different story...
Generic Method Pointers
Lets quickly revisit method pointers, which are quite useful, and often used in generic contexts:
template<class T, class Method> void callMe(T* t, Method& m) { (*t.m)(); } Foo maybe; callMe(&maybe,&Foo::bar);
Of course, this is a very simple version and overview on method pointers. In a generic context, the concrete Type Foo would be exchanged against the generic type, e.g. "T": &T::foo; So, when ever the address of a method is taken, its also bound to a type. In our template, the generic type for the method pointer should only be known at the method template level. Of course one could add it to the class template too, but then you'd have to use a different visitor instance for each type. So, at least currently, C++ has no (to me) known mechanism to have a method pointer like interface, which is later bound to a concrete type. One would have to write a callable for each member function to invoke. The template class for a generic_method_visitor would take this callable, and invoke it via the callable operator().
If one only could declare local callable constructs with a generic parameter... Unfortunately, my code is still in C++11. But in C++14 there is with generic lambdas a possible solution. Or at least, a solution, that has an acceptable interface. A language solution would be better, but this seems not possible with the current C++ Standard.
So, thanks to online compilers, every one can play around with the newest standards, and so I tried to see how a possible C++14 solution would look like, using generic lambdas. My generic coding knowledge is a little rough on the edges, so this is only a proof of concept, which shows, that a generic lambda is able to be passed to such a generic visitor. During the discussion at twitter different ideas came up, and the twitter user oliora came up with a better solution, using a make function taking a forwarding reference, so that you can define the lambda in the call of the make function. I merged his solution with my test code for boost::variant, resulting in the following example. There is some mock code, a class Person and a class Dir, with a method called name()const, returning just some string for testing. The forwarding_visitor template is also a bit more generic, it allows moving the lambda into the visitor, my version made a copy:
template<class Result, class Func> struct forwarding_visitor : boost::static_visitor<Result> { Func func; forwarding_visitor(const Func& f):func(f){} forwarding_visitor(Func&& f):func(std::move(f)){} template<class Arg> Result operator()(Arg && arg) const { return func(std::forward<Arg>(arg)); } };
I added the constructors enabling of both, copying and moving the lambda into the visitor. Making the argument to the method template a forwarding reference is nice, but afaik in the context of static_visitor there should never be an rvalue reference passed to the visitor. The Result type could be made optional by deducing it from the actual lambda type. This is already usable, but adding a make_forwarding_visitor template function eases the interface a little bit:
template<class Result, class Func> forwarding_visitor<Result, std::decay_t<Func> > make_forwarding_visitor(Func && func)
{ return {std::forward<Func>(func)}; }
This make function returns the actual forwarding_visitor instance, it only needs to forward the forwarding reference to it. This also avoids, that one needs to find out the type of the lambda using decltype(lambda), like in my original code. Olioras original code used C++11 std::decay, which in C++14 can be replaced with std::decay_t, omitting typing typename and ::type.
Leaves the main function, which contains the test code using and visiting boost::variant:
int main() { using variant = boost::variant< Person,Dir>; variant p = Person{}; variant d = Dir{}; auto visitor = make_forwarding_visitor< std::string>([](const auto& t){ return t.name();}); std::cout << boost::apply_visitor(visitor,p) << "\n"; std::cout << boost::apply_visitor(visitor,d) << "\n"; }
So, this is a working solution. There might still be some minor improvements possible, but it shows a way to come close to a late binding method pointer, which it self isn't possible in C++, as its impossible to obtain a pointer to a method of a yet unknown type. Maybe reflection could give us such facilities. One solution, I haven't shown yet, would be even better. But that is also not possible currently in C++: to derive a lambda from a known type, like static_visitor:
[]:boost::static_visitor< std::string>(const auto& t){return t.name();};
Wouldn't that be neat? Not sure if its possible to integrate into the language and lambda syntax, but it is possible to achieve, if one takes a little different approach. Meet the lambda visitor:
template<class Result, class lambda> struct lambda_visitor : lambda, public boost::static_visitor< Result> { lambda_visitor(const lambda& copy):lambda(copy){} lambda_visitor(const lambda&& m):lambda(std::move(m)){} };
// in main:
lambda_visitor<std::string, decltype(lambda)> lv(lambda);
std::cout << boost::apply_visitor(lv,p) << "\n";
std::cout << boost::apply_visitor(lv,d) << "\n"
One could still write a make function, but thats left to the reader as an exercise. Also, of course, I don't derive the lambda from a type, that would be an even better solution. But it works also, when I derive a type from static_visitor and the generic lambda. This should invoke one less call operator then the other solution.
The parent class could of course also be made a template parameter, to make this template even more useful:
template<class Result, class lambda, class base = boost::static_visitor< Result>> struct lambda_visitor : lambda, public base { lambda_visitor(const lambda& copy):lambda(copy){} lambda_visitor(const lambda&& m):lambda(std::move(m)){} };
Also, this solution can be now used for any generic visitor, not only for returning a certain member function of the types in the variant. But, as it turns out, it is at least for boost::variant to just give the lambda to the apply_visitor function, it seems to accept callables:
std::cout << boost::apply_visitor([](const auto& t){ return t.name();},p) << "\n";
But this is a new feature, restricted to C++14 and only available from boost 1.58. But also normal functions can be used as visitors, via boost::visitor_ptr.
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!