C++11 可变参数的 std::function 参数

28

一个名为test的函数以std::function<>作为其参数。

template<typename R, typename ...A>
void test(std::function<R(A...)> f)
{
    // ...
}

但是,如果我执行以下操作:

void foo(int n) { /* ... */ }

// ...

test(foo);
编译器(gcc 4.6.1)提示:no matching function for call to test(void (&)(int))。为了使最后一行 test(foo) 编译并正常工作,我该如何修改test()函数?在 test() 函数中,我需要类型为std::function<>f函数。
我的意思是,有没有一些模板技巧能让编译器确定函数(例如foo)的签名,并自动将其转换为 std::function<void(int)>

编辑

我希望这也适用于lambda(包括有状态和无状态的)。
4个回答

15

看起来你想使用重载(overloading)

template<typename R, typename ...A>
void test(R f(A...))
{
    test(std::function<R(A...)>(f));
}

这个简单的实现将接受大多数(如果不是全部)您尝试传递的函数。会拒绝一些特殊的函数(例如void(int...))。更多的工作将使它更加通用。


关于lambda表达式(包括有状态和无状态的)怎么样? - Daniel K.
4
@Daniel,你没有那么幸运了。或者将test定义为可接受任何东西(T)的模板。因为std::function也不会直接拒绝不兼容的函数对象,所以在此限制函数模板参数类型的目标似乎对我来说并不是太有用。 - Johannes Schaub - litb
1
我尝试使用(T),但是如何将其转换为std::function<R(A...)>?似乎可以使用std::result_of<>获取R,但是A...呢? - Daniel K.

14

std::function实现了Callable接口,也就是看起来像函数,但这并不意味着你应该要求可调用的对象必须是std::function

template< typename F > // accept any type
void test(F const &f) {
    typedef std::result_of< F( args ) >::type R; // inspect with traits queries
}

在模板元编程中,鸭子类型是最佳策略。在接受模板参数时,应该保持不具体化,并让客户端实现接口。

如果你真的需要一个 std::function,例如重新定位变量或类似的疯狂操作,并且你知道输入是原始函数指针,那么你可以分解原始函数指针类型并将其重组成一个 std::function

template< typename R, typename ... A >
void test( R (*f)( A ... ) ) {
    std::function< R( A ... ) > internal( f );
}

现在用户不能传递一个 std::function,因为它已经被封装在函数内部。你可以将现有的代码保留为另一个重载,并委托给它,但要小心保持简单的接口。

至于有状态的lambda表达式,我不知道如何处理这种情况。它们不能分解为函数指针,并且据我所知,无法查询或推断参数类型。这些信息是实例化 std::function 所必需的,好或坏都是这样。


1
我相信正确的术语应该是动态绑定 - 使用“鸭子类型”这个术语已经被弃用了。 - serup
1
@serup 谷歌 => “晚期绑定或动态绑定是一种计算机编程机制,其中在运行时按名称查找调用对象上的方法或使用参数调用的函数。” 不适用于模板。 - Potatoswatter
那么为什么要使用“鸭子类型”这个术语呢? - serup
按照惯例,我愿意听取建议... 参考链接 - Potatoswatter

6

这篇文章比较旧了,我发现关于同样的主题没有太多相关资料,因此我想写下一些注释。

在 GCC 4.8.2 上编译,以下代码是有效的:

template<typename R, typename... A>
R test(const std::function<R(A...)>& func)
{
    // ...
}

然而,你不能只是通过传递指针、lambda等来调用它。但是,下面的两个示例都可以使用它:

test(std::function<void(int, float, std::string)>(
        [](int i, float f, std::string s)
        {
            std::cout << i << " " << f << " " << s << std::endl;
        }));

同时:
void test2(int i, float f, std::string s)
{
    std::cout << i << " " << f << " " << s << std::endl;
}

// In a function somewhere:
test(std::function<void(int, float, std::string)>(&test2));

这些的缺点非常明显:你必须要显式地声明std::function,这可能看起来有点丑陋。
尽管如此,我还是用一个元组将其组合起来以调用传入的函数,并且它可以工作,只需要更加明确地说出你在调用测试函数时正在做什么。
如果您想使用包括元组的示例代码,请点击以下链接: http://ideone.com/33mqZA

2
仅仅为了好玩,我使用 index_sequence(在 C++14 中添加,但在 C++11 中也很容易实现)和一个 function_traits 风格的结构提出了一种方法,可以使用任何 lambda、functor 或函数。http://ideone.com/LNpj74 展示了一个工作示例。然而,请注意,对于具有 2 个或更多 operator() 重载的 functors,它仍需要一个额外的接口来指定要使用的类型。我没有尝试过使用多态 functors,但我认为那也会导致问题... - user3694249
正如Potatoswatter所暗示的那样,当模板可用时,使用std::function会产生大量的动态分配开销和毫无益处的样板文件。最好只需制作一个通用函数模板,可以接受lambda,而不是在各个地方引入std::function - underscore_d

4

通常情况下,除非你处于“二进制分界”的位置(例如动态库,“不透明”的 API),否则不建议按值接受std::function,因为正如你刚刚见证的那样,它们会对重载造成极大的麻烦。当函数确实通过值接受std::function时,通常是调用者的负担来构造对象,以避免重载问题(如果该函数真的被重载了)。

但由于您编写了一个模板,很可能您并没有使用std::function(作为参数类型)来发挥其类型抹消的优势。如果您想要检查任意的函数对象,则需要一些特性。例如,Boost.FunctionTypes 具有诸如result_typeparameter_types之类的特性。以下是一个简单、功能齐全的示例:

#include <functional>

#include <boost/function_types/result_type.hpp>
#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/function_type.hpp>

template<typename Functor>
void test(Functor functor) // accept arbitrary functor!
{
    namespace ft = boost::function_types;

    typedef typename ft::result_type<Functor>::type result_type;
    typedef ft::parameter_types<Functor> parameter_types;
    typedef typename boost::mpl::push_front<
        parameter_types
        , result_type
    >::type sequence_type;
    // sequence_type is now a Boost.MPL sequence in the style of
    // mpl::vector<int, double, long> if the signature of the
    // analyzed functor were int(double, long)

    // We now build a function type out of the MPL sequence
    typedef typename ft::function_type<sequence_type>::type function_type;

    std::function<function_type> function = std::move(functor);
}

作为最后的说明,我不建议在一般情况下内省函数对象(即探测其结果类型和参数类型),因为这对于多态函数对象根本行不通。考虑到存在多个重载的 operator(),那么就没有“规范”的结果类型或参数类型。使用C++11时,最好“急切地”接受任何类型的函数对象,或者根据需要使用SFINAE或static_assert等技术来限制它们,并在稍后(当参数可用时)使用std::result_of来检查给定参数集的结果类型。需要提前约束的一个案例是将函数对象存储到例如std::function<Sig>的容器中。
要了解我上面所说的内容,只需使用多态函数对象测试上面的片段即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接