Yes, having static functions is a “solution”. I use quotes, since with that solution, you loose the context (a.k.a. this
) and access to all non-static members (variables, functions), likely the main reason you wanted to have the handler implemented inside a class.
There is no easy good way around that, the VEX V5 APIs generally only allow to register C-style callbacks and mostly w/o providing a means of passing the context through. The problem boils down to the information loss. Whenever you want to call an instance member function on an object, you need two pieces, the object identity and the function to call.
Say you have a class like this:
class MySubsystem {
int id;
public:
MySubsystem(int _id) : id(_id) {}
void handleEvent() { printf("Event handled by %d\n", id); }
};
to call the function from your code, you’d have an instance somewhere (or a few of them) and use it to call the function, like:
MySubsystem subA(1);
MySubsystem subB(2);
subA.handleEvent();
subB.handleEvent();
If you need something else to call your member function, you’d need to give it both pieces. But the vex::controller::button::pressed function only supports a simple C-style callback. Or rather half thereof. So let’s take a bit of history detour here.
In the pre-C++ times, the C programmers didn’t have objects, but still had the need to register callbacks and have context available to those callbacks. Objects were emulated by structures, member functions by functions having explicit context argument. Like:
typedef struct {
int id;
} MySubsystem;
void registerCallback( void (*callback)(void*), void *ctx);
void handleEvent(void *ctx) {
MySubsystem *self = (MySubsystem*)ctx;
printf("Event handled by %d\n", self->id);
}
[...]
MySubsystem subA = {1};
registerCallback(handleEvent, &subA);
As you see, the code provides both pieces needed to the registration function, so that thingy can remember and provide the context later on while calling the handler.
C++ solves this need differently. It can use different idioms:
- A callback interface with virtual functions, such as:
class ButtonHandler { public: virtual void handleEvent() = 0; }
// callback registration signature
void registerCallback(ButtonHandler*);
// your implementation:
class MySubsystem : public ButtonHandler {
[...]
virtual void handleEvent() { printf("Event handled by %d\n", id); }
};
// your code registering your handler
MySubsystem subA(1);
registerCallback(&subA);
- Generalized callback using std::function, perhaps even paired with lambdas:
// callback registration signature
void registerCallback(std::function<void()> handler);
// registering your callback
MySubsystem subA(1);
registerCallback(std::bind(&MySubsystem::handleEvent, &subA);
- Of course C++, being a superset of C, would still allow full C-style callback registration (as seen in
vex::thread
class)
But for whatever reason, VEX decided to produce a C++ platform, but implement broken, half-baked C-style callbacks. So if you need to use them, you need to implement workarounds to restore the context It won’t be pretty, it would promote bad design, but it would get stuff done.
For your ControllerInputClass use case, it might look like this:
class ControllerInputClass {
static ControllerInputClass* s_self;
public:
ControllerInputClass() {
// assert s_self == nullptr; // only one instance of this singleton allowed
s_self = this;
}
void handleButton(V5_ControllerIndex button) { ... }
static void buttonAPressed() { // trampoline for Button A
if (s_self) s_self->handleButton(ButtonA);
}
static void buttonbPressed() { // trampoline for Button B
if (s_self) s_self->handleButton(ButtonB);
}
// [.... etc ...]
};
… which is a lot to get a sane design, so the general VEX expectation is that you’d just use globals everywhere (like the generated robot-config.cpp), use no context in the handlers and directly access the global objects).
Simple? Yes.
Promoting bad coding practices? Sure!