Stéphane
Stéphane

Reputation: 20370

Gtk+ g_signal_connect() and C++ lambda results in "invalid cast" errors

I would like to use lambdas with g_signal_connect() from Gtk+. Traditionally works like this to setup a callback function:

#include <gtk/gtk.h>
#include <iostream>

void my_callback(GtkApplication *app, gpointer user_data)
{
    std::cout << "test1" << std::endl;
}

void test1()
{
    GtkApplication *app = gtk_application_new(nullptr, G_APPLICATION_NON_UNIQUE);
    void * data = nullptr; // simple example
    g_signal_connect(app, "activate", G_CALLBACK(my_callback), data);
}

Above test is compiled with g++ $(pkg-config --cflags --libs gtk+-3.0) test.cpp to get the necessary GTK+-3.0 definitions.

In attempting to convert my_callback1() to a lambda, I tried variations of this:

void test2()
{
    GtkApplication *app = gtk_application_new(nullptr, G_APPLICATION_NON_UNIQUE);
    void * data = nullptr; // simple example

    // use lambda instead of call to explicit function
    g_signal_connect(app, "activate",
        G_CALLBACK(
            [](GtkApplication *application, gpointer user_data)
            {
                std::cout << "test2" << std::endl;
            }
        ), data);
}

The test2() code above produces the following compile error:

/usr/include/glib-2.0/gobject/gclosure.h:70:41: error: invalid cast
from type ‘test2()::<lambda(GtkApplication*, gpointer)>’
to type ‘GCallback {aka void (*)()}’

Is there a way to specify a C++ lambda as the callback function? I don't understand what is needed to fix this "invalid cast".

Upvotes: 5

Views: 1946

Answers (2)

Aconcagua
Aconcagua

Reputation: 25536

Problem is a pointer type mismatch, as the error message clearly tells:

invalid cast from type ‘test2()::<lambda(GtkApplication*, gpointer)>’
                                         ^ accepting 2 parameters^
to type ‘GCallback {aka void (*)()}’
                                 ^ no parameter...

The more interesting question now would be: Why did the first attempt work then? Well, obviously, the C style cast in the conversion macro acts as reinterpret_cast to another function pointer. This is possible for raw function pointers, not so, however, with lambdas...

To get around, the lambda first must be casted to appropriate function pointer; you might try this piece of code for illustration:

int main(int argc, char* argv[])
{
    void(*f)(void) = reinterpret_cast<void(*)(void)>
    (
        static_cast<void(*)(int)>([](int n){ std::cout << n; })
    );
    reinterpret_cast<void(*)(...)>(f)(7);
    std::cout << std::endl;
    return 0;
}

(used C++ cast instead of C style cast here).

Upvotes: 2

When you look at the definition of G_CALLBACK, you see that it's merely a cast to a void (*)(). This is Gtk+ employing a form of type erasure on the pointer types it receives, removing the argument list.

The lambda defines a (closure) object type. It's not a function. And while a capture-less lambda does have an implicit conversion operator to a function pointer, that pointer has a signature that matches the lambda's parameter list.

So you may convert the lambda to a void(*)(GtkApplication*, gpointer), but not directly to a void (*)() since it's a completely unrelated type.

The workaround is to make the lambda convert into a function pointer type before feeding it to G_CALLBACK for the cast. One neat trick that does it, is appending a + before the lambda:

g_signal_connect(app, "activate",
       G_CALLBACK(
           +[](GtkApplication *application, gpointer user_data)
           {
               std::cout << "test2" << std::endl;
           }
       ), data);

Since unary + isn't overloaded for lambdas, the compiler is being helpful and does a conversion to a pointer for us (something unary + may be applied to). After that, the cast in the macro should work.

Upvotes: 14

Related Questions