可变函数接受函数/可调用对象

时间:2013-08-06 10:13:33

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

问题

我希望创建一个函数,它接受任意数量的仿函数对象,或者更通常只是可调用对象(不同类型),并将它们应用于内部数据结构。该函数将在我的代码中的不同点使用不同数量的仿函数。

而不是让不同的版本接受1,2,3 ......等重复的代码我想到了使用可变参数模板。

我找到了一个解决方案,我将在下面发布作为答案,因为我在Google上找不到任何具体的内容,而其他人可能会觉得它很有用。但是,如果有人有任何更好的想法发布,请。我觉得应该有一个很好的方法来做到这一点?

天真的尝试

我知道这不行,但我的第一次尝试是

#include <iostream>
using namespace std;

struct FunctorA {
    void operator() () {
        cout << "FunctorA" << endl;
    }
};

struct FunctorB {
    void operator() () {
        cout << "FunctorB" << endl;
    }
};

template<typename... Fs>
void apply_functors(Fs... fs) {
     fs()...;   // will not work - can only expand packs in certain situations (e.g. as funciton 
}

int main(void) {
    apply_functors(FunctorA(),FunctorB());
    apply_functors(FunctorA());
    apply_functors([]()->void{ cout << "Lambda" << endl; });
    return 0;
}

然而,这不起作用,因为我们只允许在certain situations中扩展参数包,并且像这样不是免费的。

尝试2

我的下一个想法是制作一个虚拟函数,除了我可以将仿函数作为参数传递然后将它们扩展之外什么都不做。这背后的原因是函数参数是我们可以扩展参数包的情况之一。

template<typename... Fs>
void _dummyF_impl(Fs... fs){}

template<typename... Fs>
void apply_functors(Fs... fs) {
    _dummyF_impl( fs()... );    // only works if all functors return a value (else we get "invalid use of 'void'")
}

但是,这不起作用,因为仿函数从operator()返回void,我们不能将void作为参数传递给函数。

3 个答案:

答案 0 :(得分:14)

在支撑的初始化程序中扩展参数包具有保证从左到右评估的额外好处(对于函数参数列表不是这种情况)。您还应该考虑使用完美转发here's why

#include <initializer_list>  //
#include <utility>  // std::forward

template<typename... Fs>
void apply_functors(Fs&&... fs)
{
     auto list = { (std::forward<Fs>(fs)(), 0)... };
     //   ^^^^ deduced as std::initializer_list
}

原则上同样的事情,但这次没有必要的<initializer_list>包含是一个可变参数模板构造函数,你也可以使用支撑初始化程序调用:

struct dummy {
    template<typename... Fs>
    dummy(Fs&&... fs) { }
};

template<typename... Fs>
void apply_functors(Fs&&... fs)
{
     dummy { (std::forward<Fs>(fs)(), 0)... }; // evaluated left-to-right, too
}

答案 1 :(得分:5)

我会这样做

template<typename... Fs>
void apply_functors(Fs... fs) {
    int i[] = { ((void) fs(), 0)... };
    (void) i; // prevents a warning about i not being used.
}

参数包扩展发生的一种情况是在撑杆初始化器内部。上面我在数组初始化中使用它。

模式(fs(), 0)...已扩展为(f1(), 0), (f2(), 0), ..., (fn(), 0),其中f1,...,fn是提供的可调用对象。请注意,评估表达式(f1(), 0)调用f1(),忽略其返回值,表达式的结果为0int)。

需要更复杂的模式((void) fs(), 0)...来防止转角情况,即当fs()返回逗号运算符过载的类型时。

关于使用虚函数的解决方案的额外优势在于,在数组初始化(从左到右)时,标准未指定评估函数参数的顺序。例如,请注意在apply_functors(FunctorA(),FunctorB());之前调用Dansolution输出FunctorB FunctorA,而此处此解决方案的输出为FunctorA FunctorB

更新:在阅读jroksolution后,我意识到需要完美转发。所以我的更新解决方案将是

template<typename... Fs>
void apply_functors(Fs&&... fs) {
    int i[] = { ((void) std::forward<Fs>(fs)(), 0)... };
    (void) i; // prevents a warning about i not being used.
}

答案 2 :(得分:4)

我的解决方案

我找到的解决方案是the comma operator。我以前从来没有真正有理由使用它,但它很适合这里。

逗号运算符可用于评估单独的语句,但随后“返回”最后一个表达式的值,因此a,b,c的计算结果为c

因此我们可以使用它来评估我们的仿函数,忽略void返回值,然后返回传递给_dummyF_impl的东西:

template<typename... Fs>
void apply_functors(Fs... fs) {
   _dummyF_impl( (fs(),0)... );
}

这在g ++ 4.7.3下编译很好,运行时输出正如我们所期望的那样:

FunctorB
FunctorA
FunctorA
Lambda