28

I want to inline visitation of variant types with lambdas. At the moment i have the following code:

struct Foo {
    boost::variant< boost::blank , int , string , vector< int >  > var;

    template <typename T, typename IL , typename SL , typename VL>
    void ApplyOptionals( T& ref, IL&& intOption , SL&& stringOption , VL&& vectorOption ) {
        if (var.which() == 1) {
            intOption( ref , boost::get< int >(var) );
        } else if (var.which() ==2) {
            stringOption( ref , boost::get< string>(var) );
        } else if (var.which() == 3) {
            vectorOption( ref , boost::get< vector< int > >(var) );
        }
    };
};
// ...
myFooV.ApplyOptionals(
    obj,
    [](Obj& o, int v) -> void { cout << "int: " << v << endl; o.value = v; },
    [](Obj& o, string v) -> void{ cout << "string: " << v << endl; o.name = v; },
    [](Obj& o, vector< int >& v) -> void{ v.push_back(257); cout << " vector.. has elements: " << v.size() << endl; o.ids = v; }
);

However the main drawback of this approach is that it depends on the order of variant type parameters and doesn't detect at compile-time unhandled types like boost::static_visitor would do

Can i get the best of both approaches?


Working on the excellent answer from RMartinho, i'm trying to work out this error, it seems that variant thinks the operator() calls are amibiguous (i'm using g++ 4.5.1, it's like it couldn't see the lambda operators.

looking at this question request for member `...' is ambiguous in g++, it seems that c++ doesn't like multiple inheritance as a way to provide multiple overloads (even if the calls are completely non-ambiguous because of different signature)

#include <iostream>
#include <string>
#include <vector>
#include <boost/variant.hpp>

using namespace std;

typedef boost::variant< boost::blank , int , string , vector< int >  > var_t;

template <typename ReturnType, typename... Lambdas>
struct lambda_visitor : public boost::static_visitor<ReturnType>, public Lambdas... {
    lambda_visitor(Lambdas... lambdas) : Lambdas(lambdas)... { }
};

template <typename ReturnType>
struct lambda_visitor<ReturnType> : public boost::static_visitor<ReturnType> {
    lambda_visitor() {}
};


template <typename ReturnType, typename... Lambdas>
lambda_visitor<ReturnType, Lambdas...> make_lambda_visitor(Lambdas... lambdas) {
    return { lambdas... };
    // you can use the following instead if your compiler doesn't
    // support list-initialization yet
    // return lambda_visitor<ReturnType, Lambdas...>(lambdas...);
}

int main() {
    vector< int > vit;
    vit.push_back(7);

    var_t myFooV = vit;

    auto visitor = make_lambda_visitor<void>(
        [](int v) -> void { cout << "int: " << v << endl; },
        [](string& v) -> void{ cout << "string: " << v << endl; },
        [](vector< int >& v) -> void{ v.push_back(27); boost::get< vector< int > >(myFooV).push_back(34);  cout << " vector.. has elements: " << v.size() << endl; }
    );

    cout << " and for the grand finale.. " << endl;

    boost::apply_visitor( visitor , myFooV );
};

This, gives me roughly a bunch of template boost errors, but the distinct part is:

boost_1_46_0/boost/variant/variant.hpp:832:32: error: request for member ‘operator()’ is ambiguous
test2.cpp:44:54: error: candidates are: main()::<lambda(std::vector<int>&)>
test2.cpp:43:47: error:                 main()::<lambda(std::string&)>
test2.cpp:42:55: error:                 main()::<lambda(int)>
boost_1_46_0/boost/variant/variant.hpp:832:32: error: return-statement with a value, in function returning 'void'

This is the whole error, just in case i'm missing some other relevant info:

boost_1_46_0/boost/variant/variant.hpp: In member function ‘boost::detail::variant::invoke_visitor<Visitor>::result_type boost::detail::variant::invoke_visitor<Visitor>::internal_visit(T&, int) [with T = std::vector<int>, Visitor = lambda_visitor<void, main()::<lambda(int)>, main()::<lambda(std::string&)>, main()::<lambda(std::vector<int>&)> >, boost::detail::variant::invoke_visitor<Visitor>::result_type = void]’:
boost_1_46_0/boost/variant/detail/visitation_impl.hpp:130:9:   instantiated from ‘typename Visitor::result_type boost::detail::variant::visitation_impl_invoke_impl(int, Visitor&, VoidPtrCV, T*, mpl_::true_) [with Visitor = boost::detail::variant::invoke_visitor<lambda_visitor<void, main()::<lambda(int)>, main()::<lambda(std::string&)>, main()::<lambda(std::vector<int>&)> > >, VoidPtrCV = void*, T = std::vector<int>, typename Visitor::result_type = void, mpl_::true_ = mpl_::bool_<true>]’
boost_1_46_0/boost/variant/detail/visitation_impl.hpp:173:9:   instantiated from ‘typename Visitor::result_type boost::detail::variant::visitation_impl_invoke(int, Visitor&, VoidPtrCV, T*, NoBackupFlag, int) [with Visitor = boost::detail::variant::invoke_visitor<lambda_visitor<void, main()::<lambda(int)>, main()::<lambda(std::string&)>, main()::<lambda(std::vector<int>&)> > >, VoidPtrCV = void*, T = std::vector<int>, NoBackupFlag = boost::variant<boost::blank, int, std::basic_string<char>, std::vector<int> >::has_fallback_type_, typename Visitor::result_type = void]’
boost_1_46_0/boost/variant/detail/visitation_impl.hpp:260:1:   instantiated from ‘typename Visitor::result_type boost::detail::variant::visitation_impl(int, int, Visitor&, VoidPtrCV, mpl_::false_, NoBackupFlag, Which*, step0*) [with Which = mpl_::int_<0>, step0 = boost::detail::variant::visitation_impl_step<boost::mpl::l_iter<boost::mpl::l_item<mpl_::long_<4l>, boost::blank, boost::mpl::l_item<mpl_::long_<3l>, int, boost::mpl::l_item<mpl_::long_<2l>, std::basic_string<char>, boost::mpl::l_item<mpl_::long_<1l>, std::vector<int>, boost::mpl::l_end> > > > >, boost::mpl::l_iter<boost::mpl::l_end> >, Visitor = boost::detail::variant::invoke_visitor<lambda_visitor<void, main()::<lambda(int)>, main()::<lambda(std::string&)>, main()::<lambda(std::vector<int>&)> > >, VoidPtrCV = void*, NoBackupFlag = boost::variant<boost::blank, int, std::basic_string<char>, std::vector<int> >::has_fallback_type_, typename Visitor::result_type = void, mpl_::false_ = mpl_::bool_<false>]’
boost_1_46_0/boost/variant/variant.hpp:1776:13:   instantiated from ‘static typename Visitor::result_type boost::variant<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19>::internal_apply_visitor_impl(int, int, Visitor&, VoidPtrCV) [with Visitor = boost::detail::variant::invoke_visitor<lambda_visitor<void, main()::<lambda(int)>, main()::<lambda(std::string&)>, main()::<lambda(std::vector<int>&)> > >, VoidPtrCV = void*, T0_ = boost::blank, T1 = int, T2 = std::basic_string<char>, T3 = std::vector<int>, T4 = boost::detail::variant::void_, T5 = boost::detail::variant::void_, T6 = boost::detail::variant::void_, T7 = boost::detail::variant::void_, T8 = boost::detail::variant::void_, T9 = boost::detail::variant::void_, T10 = boost::detail::variant::void_, T11 = boost::detail::variant::void_, T12 = boost::detail::variant::void_, T13 = boost::detail::variant::void_, T14 = boost::detail::variant::void_, T15 = boost::detail::variant::void_, T16 = boost::detail::variant::void_, T17 = boost::detail::variant::void_, T18 = boost::detail::variant::void_, T19 = boost::detail::variant::void_, typename Visitor::result_type = void]’
boost_1_46_0/boost/variant/variant.hpp:1787:13:   instantiated from ‘typename Visitor::result_type boost::variant<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19>::internal_apply_visitor(Visitor&) [with Visitor = boost::detail::variant::invoke_visitor<lambda_visitor<void, main()::<lambda(int)>, main()::<lambda(std::string&)>, main()::<lambda(std::vector<int>&)> > >, T0_ = boost::blank, T1 = int, T2 = std::basic_string<char>, T3 = std::vector<int>, T4 = boost::detail::variant::void_, T5 = boost::detail::variant::void_, T6 = boost::detail::variant::void_, T7 = boost::detail::variant::void_, T8 = boost::detail::variant::void_, T9 = boost::detail::variant::void_, T10 = boost::detail::variant::void_, T11 = boost::detail::variant::void_, T12 = boost::detail::variant::void_, T13 = boost::detail::variant::void_, T14 = boost::detail::variant::void_, T15 = boost::detail::variant::void_, T16 = boost::detail::variant::void_, T17 = boost::detail::variant::void_, T18 = boost::detail::variant::void_, T19 = boost::detail::variant::void_, typename Visitor::result_type = void]’
boost_1_46_0/boost/variant/variant.hpp:1810:52:   instantiated from ‘typename Visitor::result_type boost::variant<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19>::apply_visitor(Visitor&) [with Visitor = lambda_visitor<void, main()::<lambda(int)>, main()::<lambda(std::string&)>, main()::<lambda(std::vector<int>&)> >, T0_ = boost::blank, T1 = int, T2 = std::basic_string<char>, T3 = std::vector<int>, T4 = boost::detail::variant::void_, T5 = boost::detail::variant::void_, T6 = boost::detail::variant::void_, T7 = boost::detail::variant::void_, T8 = boost::detail::variant::void_, T9 = boost::detail::variant::void_, T10 = boost::detail::variant::void_, T11 = boost::detail::variant::void_, T12 = boost::detail::variant::void_, T13 = boost::detail::variant::void_, T14 = boost::detail::variant::void_, T15 = boost::detail::variant::void_, T16 = boost::detail::variant::void_, T17 = boost::detail::variant::void_, T18 = boost::detail::variant::void_, T19 = boost::detail::variant::void_, typename Visitor::result_type = void]’
boost_1_46_0/boost/variant/detail/apply_visitor_unary.hpp:60:43:   instantiated from ‘typename Visitor::result_type boost::apply_visitor(Visitor&, Visitable&) [with Visitor = lambda_visitor<void, main()::<lambda(int)>, main()::<lambda(std::string&)>, main()::<lambda(std::vector<int>&)> >, Visitable = boost::variant<boost::blank, int, std::basic_string<char>, std::vector<int> >, typename Visitor::result_type = void]’
test2.cpp:49:40:   instantiated from here
boost_1_46_0/boost/variant/variant.hpp:832:32: error: request for member ‘operator()’ is ambiguous
test2.cpp:44:54: error: candidates are: main()::<lambda(std::vector<int>&)>
test2.cpp:43:47: error:                 main()::<lambda(std::string&)>
test2.cpp:42:55: error:                 main()::<lambda(int)>
boost_1_46_0/boost/variant/variant.hpp:832:32: error: return-statement with a value, in function returning 'void'

conclusion:

I want to add the final version of this utility, including tests:

lambda_visitor.h

#include <boost/variant.hpp>

namespace Variant {
    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();
        typedef ReturnType ReturnType_t;

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

        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();
        typedef ReturnType ReturnType_t;

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

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

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

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

    template <typename ReturnType>
    struct default_blank_visitor {

        typedef ReturnType ReturnType_t;
        inline ReturnType operator() (const boost::blank&) const {
            return (ReturnType) 0;
        };
    };

    template<>
    struct default_blank_visitor<void> {

        typedef void ReturnType_t;
        inline void operator() (const boost::blank&) const {};
    };

    template <typename ReturnType, typename... Lambdas>
    lambda_visitor<ReturnType, default_blank_visitor< ReturnType >, Lambdas...> make_lambda_visitor(Lambdas... lambdas) {
        return
        {
            default_blank_visitor<ReturnType > (), lambdas...
        };
        // you can use the following instead if your compiler doesn't
        // support list-initialization yet
        //return lambda_visitor<ReturnType, default_blank_visitor<ReturnType> , Lambdas...>( default_blank_visitor<ReturnType>(), lambdas...);
    };
    /*
    template <typename ReturnType, typename... Lambdas>
    lambda_visitor<ReturnType, default_blank_visitor< ReturnType >, Lambdas...> make_lambda_visitor(Lambdas && ... lambdas) {
        return
        {
            default_blank_visitor<ReturnType > (), lambdas...
        };
        // you can use the following instead if your compiler doesn't
        // support list-initialization yet
        //return lambda_visitor<ReturnType, default_blank_visitor<ReturnType> , Lambdas...>( default_blank_visitor<ReturnType>(), lambdas...);
    };*/

    template <typename ReturnType, typename... Lambdas>
    lambda_visitor<ReturnType, Lambdas...> make_lambda_visitor_override_blank(Lambdas... lambdas) {
        return
        {
            lambdas...
        };
        // you can use the following instead if your compiler doesn't
        // support list-initialization yet
        //return lambda_visitor<ReturnType, Lambdas...>(lambdas...);
    }


    namespace basic_usage 
    {
        struct Test
        {
            typedef boost::variant< boost::blank , int , double > variant_t;
            void run()
            {
                variant_t a, b, c;
                a = 42;
                b = 3.14159265;
                auto visitor = Variant::make_lambda_visitor<int>( [](int v) -> int { return v+1; } , [](double v) -> int { return (int)v*2; } );
                int result = boost::apply_visitor(visitor, a);
                HAssertMsg( result == (42 + 1) , "unexpected");
                result = boost::apply_visitor( visitor , b);
                HAssertMsg( result == 6 , "unexpected");
                auto blankVisitor = Variant::make_lambda_visitor_override_blank<int>( 
                [](int v) -> int { return -1; } 
                , [](double v) -> int { return -1; }
                , [](boost::blank ) -> int { return 0;} );
                result = boost::apply_visitor( blankVisitor , c);
                HAssertMsg( result == 0 , "unexpected");

                //same as previous case, but using lambda coalescing :-)
                auto blankVisitor2 = Variant::make_lambda_visitor_override_blank<int>( 
                [](boost::variant< int , double >& v) -> int { return -1; } 
                , [](boost::blank ) -> int { return 0;} );
                result = boost::apply_visitor( blankVisitor2 , c);
                HAssertMsg( result == 0 , "unexpected");
                result = boost::apply_visitor( blankVisitor2 , a);
                HAssertMsg( result == -1 , "unexpected");
                result = boost::apply_visitor( blankVisitor2 , b);
                HAssertMsg( result == -1 , "unexpected");
            }
        };
    }
};
Community
  • 1
  • 1
lurscher
  • 25,930
  • 29
  • 122
  • 185
  • My gut feeling is that you can't apply lambdas here, as you could not apply a simple function, because the visitation requires a cluster of functions... hope you'll get lucky and someone has a work around :) – Matthieu M. Oct 23 '11 at 17:45
  • @lurscher A variant only know what it is at run time. You want to select code at compile time according to the type of the variant at run time?! (obviously not possible). Am I miss understanding? – selalerer Oct 23 '11 at 19:40
  • 2
    @selalerer No, he wants the compiler to make sure he didn't forget a lambda for some type of the variant. – R. Martinho Fernandes Oct 23 '11 at 20:16

1 Answers1

36

You could use variadic templates to take the lambdas, and build a variant visitor using inheritance. That would retain the compile time checks.

template <typename ReturnType, typename... Lambdas>
struct lambda_visitor : public static_visitor<ReturnType>, public Lambdas... {
    lambda_visitor(Lambdas... lambdas) : Lambdas(lambdas)... {}
};

And a little helper function to use argument type deduction (required for lambdas):

template <typename ReturnType, typename... Lambdas>
lambda_visitor<ReturnType, Lambdas...> make_lambda_visitor(Lambdas... lambdas) {
    return { lambdas... };
    // you can use the following instead if your compiler doesn't
    // support list-initialization yet
    // return lambda_visitor<ReturnType, Lambdas...>(lambdas...);
}

Now you can make visitors like this:

auto visitor = make_lambda_visitor<int>([](int) { return 42; },
                                        [](std::string) { return 17; },
                                        [](std::vector<int>) { return 23; });

Note: due to a detail of the overload resolution process that I wasn't aware of, this elegant solution causes weird ambiguity errors :(

See the follow-up question for the fix.

Community
  • 1
  • 1
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • @RMartinho, just updated the answer, showing some compilation error i get after trying this strategy – lurscher Oct 24 '11 at 00:13
  • looking at this question http://stackoverflow.com/questions/1313063/request-for-member-is-ambiguous-in-g, it seems that c++ doesn't like multiple inheritance as a way to provide multiple overloads (even if the calls are completely non-ambiguous because of different signature) – lurscher Oct 24 '11 at 00:55
  • @RMartinho, just added a separate question regarding this issue: http://stackoverflow.com/questions/7870498/using-declaration-in-variadic-template – lurscher Oct 24 '11 at 01:13
  • 1
    Very nice solution! How about proposing it on the Boost mailing list for integration in Boost.Variant? – Luc Touraille Apr 30 '12 at 14:52
  • 1
    @Luc that's an interesting idea. Luc Danton worked to make it somewhat more generic. I'll think about polishing and proposing that. – R. Martinho Fernandes Apr 30 '12 at 15:55
  • 1
    Could this use `std::common_type` and `std::result_of` to infer the return type? – ScootyPuff Aug 05 '14 at 18:12
  • Why hasn't this been proposed to Boost yet? I'll propose it myself if none of you guys do. This is so damn useful that I just can't understand why it isn't part of the lib already. – vinipsmaker Feb 27 '17 at 22:34