I am trying to make a wrapper for a Gtk click signal to hook into my event system. The Gtk way to handle click events would be like this:
auto button = new Gtk::Button();
button->signal_clicked().connect(...);
So, to add it to my system I created a function called fromSignal
which takes two inputs and I will bind them together. The problem that I am running into is on the callback();
line.
error: no match for call to '(std::_Bind<Glib::SignalProxy<void()> (Gtk::Button::*(Gtk::Widget*))()>) ()'
[build] 18 | callback();
[build] | ~~~~~~~~^~
Here is the wrapper:
template <typename T>
void _signalCallback(_Bind<Glib::SignalProxy<void()> (T::*(Widget*))()> callback) {
callback();
printf("callback\n");
}
template <typename T>
void fromSignal(Gtk::Widget* widget, Glib::SignalProxy<void()> (T::*f)()) {
auto b = std::bind(f, widget);
_signalCallback<T>(b);
}
void main() {
auto button = new Gtk::Button();
fromSignal<Gtk::Button>(button, &Gtk::Button::signal_clicked);
}
I tried changing the definition of _signalCallabck
which then gives me the following error:
void _signalCallback(function<Glib::SignalProxy<void()>()> callback);
// The error message:
error: could not convert 'b' from 'std::_Bind<Glib::SignalProxy<void()> (Gtk::Button::*(Gtk::Widget*))()>' to 'std::function<Glib::SignalProxy<void()>()>'
[build] 26 | _signalCallback<T>(b);
[build] | ^
[build] | |
[build] | std::_Bind<Glib::SignalProxy<void()> (Gtk::Button::*(Gtk::Widget*))()>
What is the correct way to do this? Am I overly complicating this?
Templates make a mess of error messages. I propose three simplifications to your example code.
First, since _signalCallback
will just invoke its parameter, it's easy to inline. So _signalCallback<T>(b)
becomes b()
. One template eliminated. Second, we can replace the fromSignal
function template with a function for the specified template argument, Gtk::Button
. The other template eliminated. However, there is one more template messing with the error message, namely std::bind
.
The third simplification is to drop std::bind
and write out the equivalent expression. (I'll also throw in an alias to make the signature easier to read.
using RetType = Glib::SignalProxy<void()>;
void fromSignal(Gtk::Widget* widget, RetType (Gtk::Button::*f)()) {
(widget->*f)();
}
Compiling this gives a more informative error message:
error: pointer to member type
RetType (Gtk::Button::)()
{aka 'Glib::SignalProxy<void()> (Gtk::Button::)()'} incompatible with object typeGtk::Widget
The compiler is complaining because you cannot take an arbitrary Widget
and expect it to have a member from Button
. Base classes cannot be used to invoke a method of a derived class. You need a derived class pointer to invoke a member function of the derived class.
Change the type of the first parameter from Gtk::Widget*
to T*
so that it corresponds to the type of the second parameter.
template <typename T>
void fromSignal(T* widget, Glib::SignalProxy<void()> (T::*f)()) {
auto b = std::bind(f, widget);
_signalCallback<T>(b);
}
A lambda might make the code easier to read.
template <typename T>
void fromSignal(T* widget, Glib::SignalProxy<void()> (T::*f)()) {
_signalCallback<T>([widget, f]() { return (widget->*f)(); });
}
If you don't like the syntax to invoke a pointer-to-member (which some people find clunky):
template <typename T>
void fromSignal(T* widget, Glib::SignalProxy<void()> (T::*f)()) {
_signalCallback<T>([widget, f]() { return std::invoke(f, widget); });
}