使用lambdas和定点组合器递归访问`std :: variant`

时间:2016-10-02 17:20:42

标签: c++ lambda variant c++17 boost-hana

我想使用lambdas和重载创建函数(例如std::variant)"递归" boost::hana::overload >。

我们假设我有一个名为my_variant的变体类型,可以存储一个int,一个floatvector<my_variant>

struct my_variant_wrapper;

using my_variant = 
    std::variant<int, float, std::vector<my_variant_wrapper>>;

struct my_variant_wrapper 
{
    my_variant _v;
};

(我使用包装器my_variant_wrapper类来递归定义变体类型。)

我想以递归方式访问变体,根据存储的类型打印不同的东西。这是working example使用基于struct的访问者:

struct struct_visitor
{
    void operator()(int x) const { std::cout << x << "i\n"; }
    void operator()(float x) const { std::cout << x << "f\n"; }

    void operator()(const std::vector<my_variant_wrapper>& x) const 
    { 
        for(const auto& y : x) std::visit(*this, y._v); 
    }
};

使用上述访问者呼叫std::visit会打印所需的输出:

my_variant v{
    std::vector<my_variant_wrapper>{
        my_variant_wrapper{45}, 
        std::vector<my_variant_wrapper>{
            my_variant_wrapper{1}, my_variant_wrapper{2}
        },
        my_variant_wrapper{33.f}
    }
};

std::visit(struct_visitor{}, v);

// Prints: 
/*
    45i
    1i
    2i
    33f
*/ 

我想使用boost::hana::overloadboost::hana::fix将访问者本地创建为一系列重载的lambdas。

fixY-combinator的一个实现,可用于在类型推导的lambdas中实现递归。 (有关详细信息,请参阅this question。)

这是我尝试过的,并且有望发挥作用:

namespace bh = boost::hana;
auto lambda_visitor = bh::fix([](auto self, const auto& x)
    {
        bh::overload(
            [](int y){ std::cout << y << "i\n"; },
            [](float y){ std::cout << y << "f\n"; },
            [&self](const std::vector<my_variant_wrapper>& y)
            {
                for(const auto& z : y) std::visit(self, z._v);  
            })(x);
    });

我的推理如下:

  • boost::hana::fix返回一个一般的通用lambda,可用作std::variant的访问者。

  • boost::hana::fix采用二进制泛型lambda,其中第一个参数是一个允许递归lambda的一元函数,第二个参数是lambda主体的初始参数。

  • 使用处理程序为boost::hana::overload内的所有可能类型调用my_variant会创建某种类型为struct_visitor的访问者。

  • self重载内使用lambda_visitor代替const std::vector<my_variant_wrapper>&应该允许递归正常工作。

  • 立即用bh::overload(...)(x)调用创建的重载应触发递归访问。

不幸的是,as you can see in this wandbox examplelambda_visitor示例无法编译,喷出了许多几乎无法解释的模板错误:

  

...

     

/usr/local/boost-1.61.0/include/boost/hana/functional/fix.hpp:74:50:错误:使用&#39; main():: [with auto:2 = boost :: hana :: fix_t&gt ;; auto:3 = int]&#39;在扣除&#39; auto&#39;之前            {return f(fix(f),static_cast(x)...); }

     

...

错误似乎与我未使用boost::hana::fix时的错误类似:

auto lambda_visitor = bh::overload(
    [](int y){ std::cout << y << "i\n"; },
    [](float y){ std::cout << y << "f\n"; },
    [](const std::vector<my_variant_wrapper>& y)
    {
        for(const auto& z : y) std::visit(lambda_visitor, z._v);  
    });

std::visit(lambda_visitor, v);
  

错误:使用&#39; lambda_visitor&#39;在扣除&#39; auto&#39;之前                        for(const auto&amp; z:y)std :: visit(lambda_visitor,z._v);

我做错了什么?是否可以使用fixoverload和一组lambdas实现本地递归变体访问

我的直觉是,lambda_visitor本来是&#34;等同于&#34; struct_visitor,这要归功于fix提供的间接。< / p>

1 个答案:

答案 0 :(得分:8)

让我们选择一个更简单的例子。我们想使用fix-point组合器实现gcd。首先可能是这样的:

auto gcd = bh::fix([](auto self, int a, int b) {
    return b == 0 ? a : self(b, a%b);
});

std::cout << gcd(12, 18);

无法使用gcc编译最终产生此错误:

/usr/local/boost-1.61.0/include/boost/hana/functional/fix.hpp:74:50: error: use of 'main()::<lambda(auto:2, int, int)> [with auto:2 = boost::hana::fix_t<main()::<lambda(auto:2, int, int)> >]' before deduction of 'auto'
         { return f(fix(f), static_cast<X&&>(x)...); }
                                                  ^

我们传递给fix()的lambda有一个推导出的返回类型。但是我们如何推断呢?只有一个return语句,那个是递归的!我们需要给编译器一些帮助。要么我们需要拆分我们的return语句,以便有一个明确的类型:

auto gcd = bh::fix([](auto self, int a, int b) {
    if (b == 0) {
        return a;
    }
    else {
        return self(b, a%b);
    }
});

或者只是明确地提供返回类型:

auto gcd = bh::fix([](auto self, int a, int b) -> int {
    return b == 0 ? a : self(b, a%b);
});

这两个选项都可以编译和工作。

您的原始示例也是如此。如果你只是指定lambda返回void,那么一切正常:

auto lambda_visitor = bh::fix([](auto self, const auto& x) -> void
//                                                        ^^^^^^^^
    {
        bh::overload(
            [](int y){ std::cout << y << "i\n"; },
            [](float y){ std::cout << y << "f\n"; },
            [&self](const std::vector<my_variant_wrapper>& y)
            {
                for(const auto& z : y) std::visit(self, z._v);  
            })(x);
    });

std::visit(lambda_visitor, v);