C ++ 11 variadic std :: function参数

时间:2012-02-11 17:16:20

标签: c++ templates c++11 variadic-templates

名为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()函数中,我需要f类型为std :: function&lt;&gt ;.

我的意思是,是否有任何模板技巧让编译器确定函数的签名(示例中为foo),并自动将其转换为std::function<void(int)>

修改

我想为lambdas(既有声明也有无状态)做这项工作。

4 个答案:

答案 0 :(得分:11)

看起来你想要使用重载

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

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

答案 1 :(得分:7)

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,因为它已封装在函数中。您可以将现有代码保留为另一个重载并仅委托给它,但要小心保持接口简单。

对于有状态的lambdas,我不知道如何处理这种情况。它们不会分解为函数指针,据我所知,参数类型无法查询或推断。无论好坏,都需要此信息来实例化std::function

答案 2 :(得分:4)

除非你处于'二元分隔'(例如动态库,'opaque'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等技术约束它们,然后在(参数可用时)使用{{1}检查给定参数集的结果类型。需要预先约束前方的情况是当目标是将仿函数存储到例如前面时。容器std::result_of

为了体验上一段的含义,用足够多态函数测试上面的片段就足够了。

答案 3 :(得分:3)

这是一个旧版本,我似乎无法在同一主题上找到很多内容,所以我想我会继续做一个注释。

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

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

但是,您不能通过传入指针,lambdas等来调用它。但是,以下两个示例都适用于它:

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