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

Posted

技术标签:

【中文标题】C++11 可变参数 std::function 参数【英文标题】:C++11 variadic std::function parameter 【发布时间】:2012-03-03 18:36:41 【问题描述】:

名为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 (&amp;)(int))

要使最后一行test(foo) 编译并正常工作,我该如何修改test() 函数?在test() 函数中,我需要f 类型为std::function&lt;&gt;

我的意思是,有什么模板技巧可以让编译器确定函数的签名(例如foo),并自动将其转换为std::function&lt;void(int)&gt;

编辑

我也想让 lambdas(有状态和无状态)也能做到这一点。

【问题讨论】:

【参考方案1】:

看起来你想使用重载

template<typename R, typename ...A>
void test(R f(A...))

    test(std::function<R(A...)>(f));

这个简单的实现将接受您将尝试传递的大部分(如果不是全部)函数。外来函数将被拒绝(如void(int...))。更多的工作会给你更多的通用性。

【讨论】:

lambdas(有状态的和无状态的)怎么样? @Daniel 你不走运。或者使test 成为接受任何东西的模板(T)。 std::function 无论如何都不会直接拒绝不兼容的函数对象,因此限制函数模板参数类型的目标在这里对我来说似乎不太有用。 我尝试使用(T),但是,如何将它变成std::function&lt;R(A...)&gt;?看来我可以使用std::result_of&lt;&gt; 得到R,但是A...【参考方案2】:

std::function 实现了 Callable 接口,即它看起来像一个函数,但这并不意味着您应该要求可调用对象是 std::functions。

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

Duck 类型是模板元编程中的最佳策略。接受模板参数时,不要具体,让客户端实现接口。

如果您真的需要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 都是必需的。

【讨论】:

我认为正确的术语是动态绑定 - 不推荐使用术语 duck typing @serup Google => "后期绑定或动态绑定是一种计算机编程机制,其中在运行时通过名称查找对象上调用的方法或使用参数调用的函数。 "不适用于模板。 那为什么要用duck typing这个词呢? @serup By convention。我愿意接受建议……【参考方案3】:

通常不建议按值接受std::function,除非您处于“二进制定界”(例如动态库、“不透明”API),因为正如您刚刚看到的那样,它们对重载造成严重破坏。当一个函数实际上按值获取 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&lt;Sig&gt; 的容器。

要了解我上一段的意思,使用多态函子测试上面的 sn-p 就足够了。

【讨论】:

【参考方案4】:

这是一篇旧文章,我似乎找不到很多关于同一主题的内容,所以我想我会继续做笔记。

在 GCC 4.8.2 上编译,以下工作:

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

    // ...

但是,您不能只通过传入指针、lambda 等来调用它。但是,以下 2 个示例都可以使用它:

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

【讨论】:

只是为了好玩,我想出了一个使用 index_sequence 的方法(在 C++14 中添加,但在 C++11 中很容易实现)和一个 function_traits 样式结构来想出一些东西这将采用任何 lambda、仿函数或函数并使用它。 http://ideone.com/LNpj74 显示了一个工作示例。但是请注意,对于具有 2+ operator() 重载的仿函数,它需要一个额外的接口来指定要使用的类型。没有尝试使用多态函子,但我希望这也会导致问题...... 正如上面提到的 Potatoswatter,当模板可用时,使用 std::function 是一堆动态分配开销和样板文件,没有任何好处。最好只制作一个可以接受 lambda 的通用函数模板,而不是到处拉std::function

以上是关于C++11 可变参数 std::function 参数的主要内容,如果未能解决你的问题,请参考以下文章

如何将 std::function 包装器转换为可变参数函数?

C++11:std::function<void()> func;

C++11 std::bind函数,std::function函数

如何存储可变参数模板参数?

如何创建一个可变的通用lambda?

C++11的std::function源码解析