【问题标题】:c++14 Variadic lambda capture for function bindingc++14 用于函数绑定的可变参数 lambda 捕获
【发布时间】:2015-12-15 23:37:05
【问题描述】:

我目前正在阅读几本书以了解 c++14 的特性。我正在尝试使用可变参数模板将参数绑定到函数。我知道如何使用 std::bind 来做到这一点,但我也想用 c++14 lambda 表达式来实现这个函数,只是为了常识和理解,以及任何可能的性能优势。我读过 lambdas 可以内联,而 std::bind 不能内联,因为它是通过调用函数指针来实现的。

这是来自 myFunctions.h 的代码:

#include <functional>

int simpleAdd(int x, int y) {
    return x + y;
}

//function signatures
template<class Func, class... Args>
decltype(auto) funcBind(Func&& func, Args&&...args);

template<class Func, class... Args>
decltype(auto) funcLambda(Func&& func, Args&&...args);

/////////////////////////////////////////////////////////////////

//function definitions
template<class Func, class... Args>
inline decltype(auto) funcBind(Func&& func, Args&&... args)
{
    return bind(forward<Func>(func), forward<Args>(args)...);
}

template<class Func, class ...Args>
inline decltype(auto) funcLambda(Func && func, Args && ...args)
{   //The error is caused by the lambda below:
    return [func, args...]() {
        forward<Func>(func)(forward<Args>(args)...);
    };
}

这是我正在运行的主要代码:

#include<iostream>
#include<functional>
#include "myFunctions.h"
using namespace std;


int main()
{
    cout << "Application start" << endl;
    cout << simpleAdd(5,7) << endl;

    auto f1 = funcBind(simpleAdd,3, 4);
    cout << f1() << endl;

    //error is occurring below
    auto f2 = funcLambda(simpleAdd, 10, -2);
    cout << f2() << endl;

    cout << "Application complete" << endl;

错误 C2665 'std::forward': 2 个重载都不能转换所有参数类型

错误 C2198 'int (__cdecl &)(int,int)': 调用的参数太少

我认为当可变参数被转发到 lambda 时可能会发生错误,但我不太确定。

我的问题是如何正确地制定这段代码,以便我可以使用 lambda 来捕获函数及其参数,并在以后调用它。

【问题讨论】:

    标签: c++ lambda c++14 variadic-templates variadic-functions


    【解决方案1】:

    我读过 lambdas 可以内联,而 std::bind 不能内联 因为它是通过调用函数指针来实现的。

    如果你将simpleAdd 传递给绑定参数的东西,那么你是否使用bind 并不重要。你认为 lambda 用func 捕获了什么?这是一个函数指针。

    lambda-vs-function-pointer 案例是关于编写bind(simpleAdd, 2, 3) 与[] { return simpleAdd(2, 3); }。或者直接绑定 [](auto&amp;&amp;...args) -&gt; decltype(auto) { return simpleAdd(decltype(args)(args)...); } 之类的 lambda 与绑定 simpleAdd(这将使用函数指针)。


    无论如何,实现它非常棘手。您不能使用按引用捕获,因为事情很容易悬空,您不能使用简单的按值捕获,因为即使对于右值也总是会复制参数,并且您不能在 init 中进行包扩展-捕获。

    这遵循std::bind 的语义(调用函数对象并将所有绑定参数作为左值传递),除了 1) 它不处理占位符或嵌套绑定,以及 2) 函数调用运算符始终为 const :

    template<class Func, class ...Args>
    inline decltype(auto) funcLambda(Func && func, Args && ...args)
    {   
        return [func = std::forward<Func>(func), 
                args = std::make_tuple(std::forward<Args>(args)...)] {
            return std::experimental::apply(func, args);
        };
    }
    

    cppreference 有一个std::experimental::apply 的实现。

    请注意,这会像 bind 一样解开 reference_wrappers,因为 make_tuple 会这样做。

    您的原始代码崩溃了,因为 args 在 lambda 的函数调用运算符中是 const(默认为 const),而 forward 最终试图抛弃 constness。

    【讨论】:

    • 感谢您的回答。与使用 std::bind 相比,使用 lambda 捕获比我预期的要复杂得多。我尝试使用来自 cppreference.com 的 apply 和 invoke 的实现来实现您的代码,但我收到了更多编译器错误:错误 C2893 无法专门化函数模板'unknown-type std::invoke(_Callable &&,_Types &&... )',以及错误 C2975 '_Size':'std::make_index_sequence' 的模板参数无效,预期的编译时常量表达式。稍后我会再看一遍,但现在我会坚持使用 bind。
    【解决方案2】:

    你使用一个元组:

    template<class Func, class ...Args>
    inline decltype(auto) funcLambda(Func && func, Args && ...args)
    {   //The error is caused by the lambda below:
        auto tpl = make_tuple(std::forward<Args>(args)...);
    
        //Use move just in case Args has move-only types.
        return [func, tpl = move(tpl)]() {
            apply(func, tpl);
        };
    }
    

    apply 是defined something like this:

    namespace detail {
    template <class F, class Tuple, std::size_t... I>
    constexpr decltype(auto) apply_impl( F&& f, Tuple&& t, std::index_sequence<I...> )
    {
      return f(std::get<I>(std::forward<Tuple>(t))...);
    }
    } // namespace detail
    
    template <class F, class Tuple>
    constexpr decltype(auto) apply(F&& f, Tuple&& t)
    {
        return detail::apply_impl(std::forward<F>(f), std::forward<Tuple>(t),
            std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>>::value);
    }
    

    apply 是库 TS 版本之一的功能。在 C++17 中,apply_impl 可以调用 invoke,这适用于任何 callable。

    【讨论】:

    • 感谢您的回答。用于捕获 func 的 lambda 捕获的第一部分是否应该说“func = forward(func)”以确保您正确转发任何 Rvalues?如果这也适用于 apply_impl,而不仅仅是“f(...”,它应该说“forward(f)(...”。
    • 我在尝试此解决方案时也会遇到编译器错误,其中一个错误类似于我在顶部的问题示例中遇到的错误:错误 C2975 '_Size': invalid template argument for 'std: :make_index_sequence'、预期的编译时常量表达式和错误 C2198 'int (__cdecl *const )(int,int)': too much arguments for call.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-29
    • 1970-01-01
    • 1970-01-01
    • 2015-06-11
    • 2012-12-31
    • 2015-09-01
    • 1970-01-01
    相关资源
    最近更新 更多