30

This question is inspired in the following solution to multiple inheritance overloading pseudo-ambiguity, which is a nice way to implement lambda visitors for boost::variant as proposed in this answer:

I want to do something like the following:

template <typename ReturnType, typename... Lambdas>
struct lambda_visitor : public boost::static_visitor<ReturnType>, public Lambdas... {
    using Lambdas...::operator(); //<--- doesn't seem to work
    lambda_visitor(Lambdas... lambdas) : boost::static_visitor<ReturnType>() , Lambdas(lambdas)... { }
};

I'm not sure what would be the right syntax of adding using clauses for packed type lists. The using clause is crucial to stop the compiler from complaining that the operator() are ambiguous, which totally are not, because they have all different signatures.

Community
  • 1
  • 1
lurscher
  • 25,930
  • 29
  • 122
  • 185

2 Answers2

36

Ok i found out a pretty decent solution:

basically i need to unpack one extra lambda case and apply the using clause to the unpacked lambda and the rest, but in this case, since i apparently i cannot make a variadic list of using declarations (at least i don't know the syntax, if its possible), the rest is wrapped by inheriting from the 'rest' case, like this:

template <typename ReturnType, typename... Lambdas>
struct lambda_visitor;

template <typename ReturnType, typename Lambda1, typename... Lambdas>
struct lambda_visitor< ReturnType, Lambda1 , Lambdas...> 
  : public lambda_visitor<ReturnType, Lambdas...>, public Lambda1 {

    using Lambda1::operator();
    using lambda_visitor< ReturnType , Lambdas...>::operator();
    lambda_visitor(Lambda1 l1, Lambdas... lambdas) 
      : Lambda1(l1), lambda_visitor< ReturnType , Lambdas...> (lambdas...)
    {}
};


template <typename ReturnType, typename Lambda1>
struct lambda_visitor<ReturnType, Lambda1> 
  : public boost::static_visitor<ReturnType>, public Lambda1 {

    using Lambda1::operator();
    lambda_visitor(Lambda1 l1) 
      : boost::static_visitor<ReturnType>(), Lambda1(l1)
    {}
};


template <typename ReturnType>
struct lambda_visitor<ReturnType> 
  : public boost::static_visitor<ReturnType> {

    lambda_visitor() : boost::static_visitor<ReturnType>() {}
};

So i can do this inductively by placing two using declarations, one from the unpacked lambda type and another from the parent class, which is actually the same class with one less lambda.

lurscher
  • 25,930
  • 29
  • 122
  • 185
  • Ah, I saw your comments in my answer and was about to post this solution right now :) Pity that elegant solution gets a bit hairy with this :( – R. Martinho Fernandes Oct 24 '11 at 01:44
  • @RMartinho, not really, still is pretty nice solution, thanks – lurscher Oct 24 '11 at 01:47
  • Thanks to you too! I learned another weird corner case of C++! :) – R. Martinho Fernandes Oct 24 '11 at 01:50
  • 7
    Note that since you have a `using` declaration to bring `operator()` into scope you can now privately inherit from the functor types. Not that interesting for closures as the type of a lambda expression is unique but your solution actually works with anything that provides an `operator()`, so a bit more of encapsulation can't hurt. And with just a bit of work you can deal with pointers/references to functions, too :) – Luc Danton Oct 24 '11 at 03:27
  • 2
    Order of elements in derivation list is other than in constructor initialisation. On my compiler (g++ 4.7.2) using Wall flag I got warning that derived types will be initialized in wrong order (-Wreorder). After correcting the order, works fine :) – ldanko Mar 06 '13 at 22:30
  • The problem is that like with any recursive solution it instantiates many more classes than needed. The saddest thing is that clang and msvc happily ignore that ambiguity and only gcc makes you suffer. – Predelnik Nov 27 '15 at 12:43
13

This is an old question and a great answer. There's one more thing we can do to improve it IMHO.

In c++14 and better we don't need to specify the return type - it can be deduced.

#include <boost/variant.hpp>
#include <type_traits>

namespace detail {

    template<typename... Lambdas>
    struct lambda_visitor;

    template<typename Lambda1, typename... Lambdas>
    struct lambda_visitor<Lambda1, Lambdas...>
        : public lambda_visitor<Lambdas...>,
          public Lambda1
    {

        using Lambda1::operator ();
        using lambda_visitor<Lambdas...>::operator ();

        lambda_visitor(Lambda1 l1, Lambdas... lambdas)
            : Lambda1(l1)
            , lambda_visitor<Lambdas...>(lambdas...) {}
    };

    template<typename Lambda1>
    struct lambda_visitor<Lambda1>
        :
            public Lambda1
    {

        using Lambda1::operator ();

        lambda_visitor(Lambda1 l1)
            : Lambda1(l1) {}
    };
}

template<class...Fs>
auto compose(Fs&& ...fs)
{
    using visitor_type = detail::lambda_visitor<std::decay_t<Fs>...>;
    return visitor_type(std::forward<Fs>(fs)...);
};

use case:

boost::variant<int, std::string> x = "foo", y = 4;

auto visitor = compose([](const int& i)
                       {
                           std::cout << i << std::endl;
                       },
                       [](const std::string& s)
                       {
                           std::cout << s << std::endl;
                       });

boost::apply_visitor(visitor, x);
boost::apply_visitor(visitor, y);
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142