代码之家  ›  专栏  ›  技术社区  ›  Stéphane

Gtk+g\U signal\U connect()和C++lambda会导致“无效强制转换”错误

  •  3
  • Stéphane  · 技术社区  · 7 年前

    我想将lambdas用于 g_signal_connect() 来自Gtk+。 通常是这样设置回调函数的:

    #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);
    }
    

    以上测试由 g++ $(pkg-config --cflags --libs gtk+-3.0) test.cpp 获取必要的GTK+-3.0定义。

    在尝试转换时 my_callback1() 对于lambda,我尝试了以下变化:

    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);
    }
    

    这个 test2() 上面的代码产生以下编译错误:

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

    有没有办法将C++lambda指定为回调函数?我不明白需要什么来修复这个“无效的演员阵容”。

    2 回复  |  直到 7 年前
        1
  •  9
  •   StoryTeller - Unslander Monica    7 年前

    当你看到 G_CALLBACK ,你看这只是 void (*)() 。这是Gtk+对接收到的指针类型采用类型擦除的形式,删除参数列表。

    lambda定义(闭包)对象类型。这不是一个函数。虽然无捕获lambda确实有到函数指针的隐式转换运算符,但该指针具有与lambda的参数列表匹配的签名。

    因此可以将lambda转换为 void(*)(GtkApplication*, gpointer) ,但不是直接到 无效(*)() 因为这是一种完全不相关的类型。

    解决方法是在将lambda馈送到之前将其转换为函数指针类型 G\U回调 对于演员。一个巧妙的技巧就是在 + 在lambda之前:

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

    由于一元+对于lambdas没有重载,编译器很有帮助,可以为我们转换为指针(一元+可以应用于某些对象)。之后,宏中的强制转换应该可以工作。

        2
  •  1
  •   Aconcagua    7 年前

    问题是指针类型不匹配,错误消息清楚地表明:

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

    现在更有趣的问题是:为什么第一次尝试成功了?很明显,转换宏中的C样式转换 reinterpret_cast 指向另一个函数指针。对于原始函数指针,这是可能的,但对于lambdas,情况并非如此。。。

    若要绕过,必须首先将lambda转换为适当的函数指针;您可以尝试以下代码进行说明:

    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;
    }
    

    (此处使用C++强制转换而不是C样式强制转换)。