传递非通用函数的最有效方法是什么?

What is the most efficient way to pass a non generic function?

我正在学习 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 ?
}

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

其他选择:

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

和往常一样,这取决于你的编译器今天有多好,将来会多好。

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

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

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

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

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

由于 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;
}

在不使用引用的情况下,复制 lambda 时将复制 BigState()。

更新: 又看了一遍题目,发现是要限制签名

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 的任何函数(可能带有隐式转换),并且 return 是 int 或任何隐式转换为 int。但是,如果您只想接受完全接受 int 和 return int 的类似函数的对象,您可以查看 lambda 是否可转换为 std::function<int(int)>

(基于评论的澄清)

可以使用 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

however I would like to restrict the function signature as part of the API design.

所以限制一下:

#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; });
}