传递非泛型函数的最有效方法是什么?

Afo*_*box 13 c++ function functor std-function

我正在学习C++中的函数式编程.我的目的是将非泛型函数作为参数传递.我知道模板方法,但是我想将函数签名限制为API设计的一部分.我在cpp.sh上编写了4个不同的方法示例:

// 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 ?
}
Run Code Online (Sandbox Code Playgroud)

以上任何一种尝试都是正确的吗?如果没有,我如何实现目标?

use*_*670 14

(根据评论的澄清)

可以使用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);
}
Run Code Online (Sandbox Code Playgroud)

在线编译器


Jar*_*d42 5

其他选择:

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


Mic*_*ler 5

像往常一样,这取决于您的编译器今天有多好,以及将来有多好.

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

所以,如果你想要最快的代码,你应该小心std::function.因此,第一个变体是最快的(在今天的编译器上):

int functor_by_value(functor_type x) {
    return x(1);
}
Run Code Online (Sandbox Code Playgroud)

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

当涉及有状态的lambdas时,你只有两种选择.将lambda作为模板参数传递,或转换为std::function.因此,如果你想用lambdas(在今天的编译器中)获得最快的代码,你就可以将函数作为模板化参数传递.

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

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;
}
Run Code Online (Sandbox Code Playgroud)

要么:

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;
}
Run Code Online (Sandbox Code Playgroud)

如果不使用引用,则在复制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();
}
Run Code Online (Sandbox Code Playgroud)

这会将它限制为任何可以接受的函数int(可能使用隐式转换),并返回int隐式转换为的任何类型int.但是,如果您只想接受完全接受int并且返回完全int 的类似函数的对象,则可以看到lambda是否可以转换为std::function<int(int)>


Ric*_*ges 5

但是我想将函数签名限制为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; });
}
Run Code Online (Sandbox Code Playgroud)

  • 当对象可以作为`int(int)`调用但不能转换为`std :: function`对象时,这将失败.[例如,当对象不可复制/移动但具有`public:int operator()(int)`](https://wandbox.org/permlink/ktOE29JgZyMtSdAM)时. (2认同)