简体   繁体   中英

C++ variable arguments

I have a class with a state machine, and want to have a single point of entry to pass events to the state machine. The event is accompanied with event specific data, which I then want to dispatch to handlers. So it looks something like this...

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);

};

...with invocations that look like this...

X x;

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

I'm having trouble figuring out how to get the template function to invoke the correct handler. If I simply switch on the eventId and pass through the variable arguments, it won't compile because handlers don't exist for all the parameter combinations (eg, there's no eventA() handler that accepts eventB() arguments, which I would not want anyway).

My guess is that there is some elegant way to do this, but it is eluding me.

Option 1. Ditch variadics

If you didn't have C++11 template parameter packs, the natural choice would be to declare a type for each event, inheriting from a base event type, and package the eventData in that. That's what I'd recommend. You can use dynamic type to verify that the right thing is getting dispatched, and if that slows things down disable the check in production mode.

Option 2. PTMF

Use a pointer-to-member-function to identify the event to the dispatcher. This eliminates the need for an enumeration, and encodes the type of the dispatch directly into the templated dispatcher.

This does, however, require that the dispatched functions be 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 );

There is no way of doing what you want by keeping the same design (ie events dispatched based on an integral value computed at run-time). The value of eventId is only known at run-time and determines the signature of the function to be invoked, but the types of the arguments must be known at compile-time. Directly or indirectly , your function would have to look something like this:

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

But that won't ever compile , because the compiler will parse each of the if branches and certainly find some call to eventX which is not compatible with its signature.

I suggest you not to use this design in the first place, and rather adopt an approach similar to the one of Boost.Signal for handling events.

I am not sure if I understand exactly what you are trying to do, but would something like this template specialization work?

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

If eEvent is integer-like and always known at compile time, you could template on that. Then your invocations would look like

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

It might get a little more complicated because of restrictions on specializing function templates. Also, you'd probably need a generic, which means the wrong argument pack would be a runtime error (but a clear one).

I'm not sure this actually has any advantage over

x.onEventA(1, 2.4);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM