Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

best way to do variant visitation with lambdas

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");
            }
        };
    }
};
like image 691
lurscher Avatar asked Oct 23 '11 16:10

lurscher


1 Answers

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.

like image 132
R. Martinho Fernandes Avatar answered Oct 16 '22 04:10

R. Martinho Fernandes