接受函子/可调用对象的可变参数函数

Variadic Function Accepting Functors/Callable Objects

本文关键字:变参 参数 函数 对象 调用      更新时间:2023-10-16

>问题

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

与其制作不同的版本来接受 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()返回 void,并且我们不能将 void 作为参数传递给函数。

大括号初始值设定项中扩展参数包具有保证从左到右计算的额外好处(函数参数列表并非如此(。您还应该考虑使用完美的转发,原因如下。

#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
}

我会这样做

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() 时会忽略其返回值,表达式的结果是 0(int(。

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

相对于使用虚拟函数的解决方案,额外的优点是,函数参数的计算顺序不是由标准指定的,而在数组初始化中是(从左到右(。例如,请注意,在 Dan 的解决方案中调用 apply_functors(FunctorA(),FunctorB()); 输出FunctorBFunctorA之前,而在此解决方案中,输出FunctorA后跟 FunctorB

更新:阅读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.
}

我的解决方案

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

逗号运算符可用于计算单独的语句,但随后"返回"最后一个表达式的值,因此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