Search code examples
c++gtkmm

Calling a method from a binding is giving a "no match for call" error


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?


Solution

  • 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 type Gtk::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); });
    }