c++ - 使用 lambda 进行变体访问的最佳方法

标签 c++ c++11 lambda visitor-pattern boost-variant

我想使用 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 运算符。

看这个问题request for member `...' is ambiguous in g++ ,似乎 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; });

注意:由于我不知道重载解决过程的细节,这个优雅的解决方案会导致奇怪的歧义错误:(

follow-up question进行修复。

关于c++ - 使用 lambda 进行变体访问的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7867555/

相关文章:

c# - 带有构造函数参数的 lambda 的 RelayCommand

c# - 事件处理程序的 Lambdas?

c# - C# 中的 C++ Armadillo 代码

c++ - 如何立即调用 C++ lambda?

c++ - "no matching function for call to ‘async(std::launch, <unresolved overloaded function type>, std::string&)’“

c++ - 通过在 C++ 中调用 vector 中对象的方法来求和值

linq - 将此 Linq 查询从查询​​语法转换为 lambda 表达式

c++ - 使用isalpha时如何出现异常

c++ - POSIX open() 卡在 SMB 共享上

c++ - 我应该如何使用 v8::SetNativesDataBlob?