13

问题

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

我考虑使用可变参数模板,而不是制作不同的版本来接受 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;
}

但是,这不起作用,因为我们只允许在某些情况下扩展参数包,并且像这样的免费不是一个。

尝试 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()的 s 返回 void,我们不能将 void 作为参数传递给函数。

4

3 回答 3

14

在大括号初始化器内扩展参数包具有保证从左到右评估的额外好处(函数参数列表不是这种情况)。您还应该考虑使用完美转发,原因如下

#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
}
于 2013-08-06T10:26:13.103 回答
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)where f1, ...,fn是提供的可调用对象。请注意,计算表达式(f1(), 0)调用f1(),忽略其返回值,表达式的结果是0(an int)。

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

关于使用虚拟函数的解决方案的额外优势是,标准未指定评估函数参数的顺序,而在数组初始化中它是(从左到右)。例如,请注意调用Danapply_functors(FunctorA(),FunctorB());解决方案输出之前,而使用此解决方案这里的输出后面是.FunctorBFunctorAFunctorAFunctorB

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

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.
}
于 2013-08-06T10:46:54.640 回答
4

我的解决方案

我找到的解决方案是逗号运算符。我以前从来没有真正使用过这个,但它很适合这里。

逗号运算符可用于计算单独的语句,然后“返回”最后一个表达式的值,因此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
于 2013-08-06T10:13:32.850 回答