在std::bind中包装lambda以捕获r值

Wrapping a lambda in std::bind to capture R-value

本文关键字:包装 std bind lambda      更新时间:2023-10-16

我有一种情况,我被困在一个c++ 11只编译器(gcc 4.7.2,所以我没有访问c++ 14广义lambda捕获),所以我使用std::bind作为一个解决方案,如果我需要捕获一个移动对象。

我从这个stackoverflow的答案中读到了这个解决方法,并且我已经成功地使用了这个技术。

然而,我有一种情况,我通过转发绑定表达式作为另一个绑定表达式的绑定参数来形成高阶函子。

我可以把它简化成一个例子。这个例子是为了回答这个问题而设计和简化的:

template <class F>
void func(F&& func)
{
    auto f = std::bind(
        [](F& f) -> void
        {
        },
        std::forward<F>(func)
    );
    f(); // causes compiler error
}
int main()
{
    func(
        std::bind(
            []() { }
        )
    );
}

由于我无法理解的原因,当我调用f()时,编译器会生成一大堆错误。我将尽量简化它,而不是粘贴整个错误消息:

test7.cpp:22:2: error: no match for call to ‘(std::_Bind<func(F&&) [with F = std::_Bind<main()::<lambda()>()>]::<lambda(std::_Bind<main()::<lambda()>()>)>(std::_Bind<main()::<lambda()>()>)>) ()’

这个错误是由GCC 4.7.2产生的。

我看不出为什么会发生这种事。在上面的人为示例中,您将注意到,在main()中,我在空lambda周围有一个额外的bind表达式。如果我删除bind表达式,并简单地传递空lambda,如:

func([]() { });

…然后没有错误发生。但同样,这是一个人为的示例—在实际应用程序中,我需要将lambda包装在bind表达式中,以便进行移动捕获。我看不出[](){}std::bind([](){})在语义上有什么不同。它们在功能上应该是等价的,即使它们的计算结果是不同的类型。

那么,为什么会出现这个错误呢?或者这仅仅是GCC的问题?(我意识到4.7.2在这一点上有点老了)

当传递给bind(f, args...)的参数之一是调用bind(g, args...)的结果时,传递给f的参数是对g求值的结果(参见[function .bind])。/10,特别是子弹10.2)。在您的程序中,计算lambda [](){}结果作为第一个参数传递给[](F& f),这当然是错误的。

不太好看的解决方案:为内部绑定表达式引入一个包装器类,它隐式地转换为引用到包装的类型:

template <class F>
class protect_bind_expression {
    F f_;
public:
    protect_bind_expression(F f) :
        f_{std::move(f)} {}
    operator F&() { return f_; }
};
template <class F>
protect_bind_expression<typename std::decay<F>::type>
protect(F&& f) {
    return {std::forward<F>(f)};
}
template <class F>
void func(F&& func)
{
    auto f = std::bind(
        [](F&){},
        protect(std::forward<F>(func))
    );
    f(); // Doesn't cause compiler error
}