对lambda进行变量访问的最好方法

best way to do variant visitation with lambdas

本文关键字:方法 访问 变量 lambda      更新时间:2023-10-16

我想用lambda内联访问不同类型。目前我有以下代码:

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; }
);

然而,这种方法的主要缺点是它依赖于变量类型参数的顺序,并且不能像boost::static_visitor那样在编译时检测未处理的类型

我能得到最好的两种方法吗?


在RMartinho的优秀答案上工作,我试图解决这个错误,似乎变体认为operator()调用是模糊的(我使用g++ 4.5.1,就像它看不到lambda操作符。

查看这个问题请求成员'…'在c++中是有歧义的,c++似乎不喜欢用多重继承来提供多重重载(即使调用因为不同的签名而完全没有歧义)

#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 );
};

这给了我一堆模板增强错误,但不同的部分是:

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'

这是整个错误,只是以防我错过了一些其他相关信息:

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'

结论:

我想添加这个实用程序的最终版本,包括测试:

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");
            }
        };
    }
};

您可以使用可变模板来获取lambda,并使用继承构建一个可变访问者。这将保留编译时检查。

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

和一个小的辅助函数来使用参数类型推导(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...);
}

现在你可以让访问者像这样:

auto visitor = make_lambda_visitor<int>([](int) { return 42; },
                                        [](std::string) { return 17; },
                                        [](std::vector<int>) { return 23; });
注意:由于我不知道重载解析过程的细节,这个优雅的解决方案会导致奇怪的歧义错误:(