ラムダ式と関数ポインタ

ラムダ式から関数ポインタへの変換

c++でgtkのコードを書いていたときのこと、GUIのイベントハンドラをc++11のラムダ式を使って次のように書きました。

g_signal_connect(widget, "toggled", 
    G_CALLBACK([](GtkToggleButton *spinbutton, gpointer data) {
        ...
    }), NULL);

しかし、これはコンパイルエラーになりました。

error: invalid cast from type ‘foo()::<lambda(GtkSpinButton*, gpointer)>’ to type ‘GCallback {aka void (*)()}

gtkではイベントハンドラはすべてG_CALLBACKを介してvoid (*)()型にキャストして渡すという、大胆な手法をとっています。

ラムダ式はキャプチャ無しの場合、同じシグネチャの関数ポインタへ暗黙的型変換が認められています。 つまり今回の場合 foo()::<lambda(GtkSpinButton*, gpointer)>から void (*)(GtkSpinButton*, gpointer)への変換は合法になります。 さらに、 void (*)(GtkSpinButton*, gpointer)からvoid (*)()への明示的型変換も一応合法です。 しかし、ラムダ式から直接void (*)()へのキャストは許されません。

よって、次のようにワンクッションおいて書けば大丈夫です。

void (*callback)(GtkToggleButton *spinbutton, gpointer data) =
    [](GtkToggleButton *spinbutton, gpointer data) {
        ...
    };
g_signal_connect(widget, "toggled", G_CALLBACK(callback), NULL);

これならラムダ式を使う意味がありませんね。 何かいい方法が無いかと調べるとstackoverflow先生にいい質問が

automatic decay of lambda to function pointer when passing to template function

回答をみるとラムダ式の前に+をつけると同じシグネチャの関数ポインタに変換されるらしい。

g_signal_connect(widget, "toggled", 
    G_CALLBACK(+[](GtkToggleButton *spinbutton, gpointer data) {
        ...
    }), NULL);

これでコンパイルが通った。すごい。

型を確認してみる(g++ 4.7.2)。

auto lamdba =  [](GtkToggleButton *spinbutton, gpointer data) {};
int status;
std::cout <<
    abi::__cxa_demangle(typeid(lamdba).name(), 0, 0, &status) << "\n" <<
    abi::__cxa_demangle(typeid(+lamdba).name(), 0, 0, &status) << std::endl;
main::{lambda(GtkToggleButton*, void*)#1}
void (*)(GtkToggleButton*, void*)

おまけ - decay operator

このような単項プラス演算子はdecay operatorと呼ばれるらしい(日本語で何というのか知ってる人は教えてください)。

decay operatorは配列型をポインタに、関数を関数ポインタに変換する。

int a[10];
std::cout <<
    abi::__cxa_demangle(typeid( a).name(), 0, 0, &status) << // int [10]
"\n" <<
    abi::__cxa_demangle(typeid(+a).name(), 0, 0, &status) << // int*
std::endl;
// void func(void);
std::cout <<
    abi::__cxa_demangle(typeid( func).name(), 0, 0, &status) << // void ()
"\n" <<
    abi::__cxa_demangle(typeid(+func).name(), 0, 0, &status) << // void (*)()
std::endl;

Comments