【问题标题】:What is the most efficient way to pass a non generic function?传递非泛型函数的最有效方法是什么?
【发布时间】:2018-08-16 09:11:07
【问题描述】:

我正在学习 C++ 中的函数式编程。我的意图是传递一个非泛型函数作为参数。我知道模板方法,但是我想将函数签名限制为 API 设计的一部分。我想出了4种不同的方法example on cpp.sh

// Example program
#include <iostream>
#include <string>
#include <functional>

typedef int(functor_type)(int);


int by_forwarding(functor_type &&x)  {
    return x(1);
}

int functor_by_value(functor_type x) {
    return x(1);
}

int std_func_by_value(std::function<functor_type> x) {
    return x(1);
}

int std_func_by_forwarding(std::function<functor_type> &&x) {
    return x(1);
}

int main()
{
    std::cout << functor_by_value([](int a){return a;}); // works
    std::cout << std_func_by_value([](int a){return a;}); // works
    std::cout << std_func_by_forwarding(std::move([](int a){return a;})); // works

    //std::cout << by_forwarding([](int a){return a;}); // how to move lambda with forwarding ?
}

以上尝试是否正确?如果没有,我该如何实现我的目标?

【问题讨论】:

  • prog.cc:29:41: warning: moving a temporary object prevents copy elision :) 叮叮当当警告!
  • 简答:int functor_by_value(functor_type x).
  • std::invoke 如果你可以访问 C++17
  • Lambda 是匿名的,所以只能通过类型推导来获取它的类型。
  • 这取决于您放入“传递非通用函数”的含义前两个变体接受引用或指向函数的指针。第 3 和第 4 个变体接受对象或对具有重载 operator () 的对象的右值引用。所以每个x 都有一个operator () 所以通常的调用语法x(1); 可以工作,但是它们都不是函数。

标签: c++ function functor std-function


【解决方案1】:

(根据 cmets 的说明)

可以使用std::is_invocable限制签名:

template<typename x_Action> auto
functor_by_value(x_Action && action)
{
    static_assert(std::is_invocable_r_v<int, x_Action, int>);
    return action(1);
}

online compiler

【讨论】:

  • @lubgr 只是一种习惯。我一直在禁止隐式对象构造的环境中工作。
【解决方案2】:

其他选择:

template <typename Func>
auto functor_by_value(Func&& f)
-> decltype(std::forward<Func>(f)(1))
{
    return std::forward<Func>(f)(1);
}

【讨论】:

    【解决方案3】:

    但是我想将函数签名限制为 API 设计的一部分。

    所以限制它:

    #include <functional>
    #include <type_traits>
    #include <iostream>
    
    /// @tparam F is a type which is callable, accepting an int and returning an int
    template
    <
        class F, 
        std::enable_if_t
        <
            std::is_convertible_v<F, std::function<int(int)>>
        >* = nullptr
    >
    int myfunc(F &&x) {
        return x(1);
    }
    
    int main()
    {
        auto a = myfunc([](int x) { std::cout << x << std::endl; return 1; });
    
        // does not compile
        // auto b = myfunc([]() { std::cout << "foo" << std::endl; return 1; });
    }
    

    【讨论】:

    • 当对象可以作为int (int) 调用但不能转换为std::function 对象时,这将失败。 For example when object is not copyable / movable but has public: int operator ()(int).
    • @VTT 我同意 - std::function 期望函数对象可以在 const 接口上调用,这可能是也可能不是标准中的错误,具体取决于您如何看待它。但是,OP 提到 std::function 是参数类型的候选者,所以我从那里带头。我看到了你的回答(并赞成)。我不知道 is_invocable_r_t 类型特征。
    【解决方案4】:

    像往常一样,这取决于你的编译器今天有多好,以及未来有多好。

    目前,编译器不太擅长优化std::function。令人惊讶的是,std::function 是一个复杂的对象,有时必须分配内存来维护有状态的 lambda 函数。它还使std::function 必须能够引用成员函数、常规函数和 lambdas 并以透明方式执行的事情变得复杂。这种透明度的运行成本很高。

    因此,如果您想要尽可能快的代码,您应该小心std::function。出于这个原因,第一个变体是最快的(在今天的编译器上):

    int functor_by_value(functor_type x) {
        return x(1);
    }
    

    它只是传递一个指向函数的指针。

    当涉及到有状态的 lambda 时,您只有两个选择。要么将 lambda 作为模板参数传递,要么转换为 std::function。因此,如果您希望使用 lambdas(在当今的编译器中)实现最快的代码,您可以将函数作为模板参数传递。

    由于 lambda 函数可能具有大状态,因此传递它可能会复制大状态(当无法复制省略时)。 GCC 将直接在参数列表上构造 lambda(没有副本),但嵌套函数将为 lambda 调用复制构造函数。为避免这种情况,要么通过 const 引用传递它(在这种情况下它不能是可变的),要么通过右值引用传递:

    template<class Func>
    void run2(const Func & f)
    {
        std::cout << "Running\n";
        f();
    }
    template<class Func>
    void run(const Func & f)
    {
        run2(f);
    }
    int main()
    {
        run([s=BigState()]() { std::cout << "apply\n"; });
        return 0;
    }
    

    或者:

    template<class Func>
    void run2(Func && f)
    {
        f();
    }
    template<class Func>
    void run(Func && f)
    {
        run2(std::forward<Func>(f));
    }
    int main()
    {
        run([s=BigState()]() { std::cout << "apply\n"; });
        return 0;
    }
    

    不使用引用,BigState() 将在复制 lambda 时被复制。

    更新: 再次阅读问题后,我看到它想限制签名

    template<typename Func, 
             typename = std::enable_if_t<
                std::is_convertible_v<decltype(Func(1)), int>>>
    void run2(const Func & f)
    {
        std::cout << "Running\n";
        f();
    }
    

    这会将它限制为可以接受int(可能带有隐式转换)的任何函数,并返回int 或任何隐式转换为int 的类型。但是,如果您只想接受完全接受 int 并完全返回 int 的类函数对象,您可以查看 lambda 是否可转换为 std::function&lt;int(int)&gt;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-18
      • 2020-12-04
      • 1970-01-01
      • 2014-06-08
      • 2014-08-15
      • 2011-11-18
      相关资源
      最近更新 更多