C ++变量参数

时间:2013-01-15 16:41:34

标签: c++ function templates variadic

我有一个带有状态机的类,并希望有一个单一的入口点来将事件传递给状态机。该事件伴随着事件特定数据,然后我想将其发送给处理程序。所以它看起来像这样......

class X
{
  public:
    ...
    template<typename... A> void fsm(eEvent eventId, const A&... eventData);

  private:
    ...
    void eventA(int a, double b);
    void eventB(std::string a);
    void eventC(unsigned long a);

};

...使用看起来像这样的调用......

X x;

x.fsm(eEventA, -1, 2.0);
x.fsm(eEventB, "xyz");
x.fsm(eEventC, 42);

我无法弄清楚如何让模板函数调用正确的处理程序。如果我只是打开eventId并传递变量参数,它将无法编译,因为所有参数组合都不存在处理程序(例如,没有eventA()处理程序接受eventB()参数,我不会无论如何都想要。)

我的猜测是,有一些优雅的方法可以做到这一点,但这是在逃避我。

[编辑]

好的,我发现了一种适合我的目的的方法。这是一个示例类来说明......

class X
{

private:

    bool a(int) { return true; }
    bool b(int) { return true; }

    template<typename... A> bool a(const A&...) { return false; }
    template<typename... A> bool b(const A&...) { return false; }

public:

    template<typename... A> void x(int i, const A&... args)
    {
        if (i == 0)
        {
            a(args...);
        }
        else
        {
            b(args...);
        }
    }
};

a()和b()的可变参数模板方法将捕获对X.x()的任何调用,其中参数不是单个int。这解决了我收到的编译器错误,并启用了我正在寻找的更简单的类API。

4 个答案:

答案 0 :(得分:4)

选项1. Ditch variadics

如果您没有C ++ 11模板参数包,那么自然的选择是为每个事件声明一个类型,从基本事件类型继承,并将eventData包装在其中。这就是我推荐的。您可以使用动态类型来验证是否正在调度正确的内容,如果这样做会减慢速度,则禁用签入生产模式。

选项2. PTMF

使用指向成员的指针函数向调度程序标识事件。这消除了枚举的需要,并将调度的类型直接编码到模板化的调度程序中。

但是,这确实要求调度函数为public

template<typename... P, typename... A> void fsm(void (X::*event)( P ... ),
    const A&... eventData) {
    this->*event( eventData ... );
}

x.fsm( &X::eventB, 5, 1.3 );

答案 1 :(得分:0)

通过保持相同的设计(即根据在运行时计算的整数值调度的事件),无法做你想要的eventId的值仅在运行时已知,并确定要调用的函数的签名,但必须在编译时知道参数的 types 直接或间接,您的功能必须如下所示:

if (eventId == eEventA) { eventA(eventData...); }
else if (eventId == eEventB) { eventB(eventData...); }
...

但是将不会编译,因为编译器将解析 if分支的每个,并且当然会找到对eventX的一些调用与其签名不兼容。

我建议您首先不要使用此设计,而是采用类似于Boost.Signal的方法来处理事件。

答案 2 :(得分:0)

我不确定我是否完全理解你想要做什么,但是这个模板专业化会有用吗?

#include <iostream>

// ----------------------------------
class EventA
{
public:
    int a;
    double b;
};

class EventB
{
public:
    std::string strEvent;
};

class EventC
{
public:
    unsigned long a;
};

// -------------------------
class X
{
public:
    template<typename EVENT_TYPE> void fsm (const EVENT_TYPE &eventData);

public:
    void eventA (int a, double b) {}
    void eventB (std::string a) {}
    void eventC (unsigned long a) {}
};


// -------------------------
template<typename TYPE>
struct invoker {};

template<>
struct invoker<EventA>
{
    static void call (X *x, EventA e)
    {
        x->eventA (e.a, e.b);
    }
};

template<>
struct invoker<EventB>
{
    static void call (X *x, EventB e)
    {
        x->eventB (e.strEvent);
    }
};

template<>
struct invoker<EventC>
{
    static void call (X *x, EventC e)
    {
        x->eventC (e.a);
    }
};


// ---------------------------------------
template<typename EVENT_TYPE> 
void X::fsm (const EVENT_TYPE &eventData)
{
    invoker<EVENT_TYPE>::call (this, eventData);
}


int main (int argc, char *argv[])
{
    X x;

    EventA eEventA;
    eEventA.a = -1;
    eEventA.b = 2.0;

    EventB eEventB;
    eEventB.strEvent = "xyz";

    EventC eEventC;
    eEventC.a = 42;

    x.fsm (eEventA);
    x.fsm (eEventB);
    x.fsm (eEventC);

    return 0;
}

答案 3 :(得分:0)

如果eEvent类似于整数并且在编译时始终是已知的,那么您可以对其进行模板化。然后你的调用看起来像

x.fsm<eEventA>(1, 2.4);

由于对特殊功能模板的限制,它可能会变得更复杂一些。此外,您可能需要一个泛型,这意味着错误的参数包将是一个运行时错误(但是一个明确的错误)。

我不确定这实际上有什么优势

x.onEventA(1, 2.4);