I find the following concept a better idea. I too wrote GUI code, and found signals and slots to be the best method.
Signals and slots makes it possible to connect multiple slots (functions called) to a signal (event ID that gets triggered). Each GUI widget contains a Signals class which holds a std::map of Signal instances. Whenever one is getting used, it will thus be allocated.
Each Signal has a std::vector of Slot instances, which is a base class for few specific slots: functions, member functions, const member functions and other signals (I added this myself, very convenient, it's not part of the signal-slot concept).
Each Slot has the parameters it needs to pass to the function / member function / signal stored as (private) data members. I allow up to 9 parameters (and only void for return type), so each slot kinds I described above has 9 classes (I do this with macro trickery, it creates classes for each version with N number of arguments).
Then I can send a signal (I call emit() on a Signal instance), it will call each of it's slots and make them call the function / member function / signal they hold. Emit does not take parameters, as the parameters are already saved in the slots.
Some implementations allow to pass parameters to emit(). This is (practically) impossible if you allow slots to point to other signals.
Example:
void f(const std::string &string){ std::cout << string << '\n';}button.signals[Events::OnClick].connect(&f, "some text");button.signals[Events::OnClick].emit(); // prints "some text"