パスワードを忘れた? アカウント作成
1354643 journal
プログラミング

Yoh2の日記: C11で疑似関数オーバーロード (できてません)

日記 by Yoh2

[2012-01-18 13:15追記: GCC拡張機能(clangでも使える)を利用して実現することができました。続・C11で疑似関数オーバーロード (GCC拡張機能使用)を参照して下さい。]

[C/C++規格: 11] C11の新機能 その2 -- 型ジェネリック式から話題を一部引っ越してきました。

C11で型ジェネリックが使えるようになったため、1引数の関数については擬似的に関数オーバーロードを実現するマクロを書けるようになった。
では引数の数が異なるものについてのオーバーロードはというと、_Genericのみで直接行うことはできない。でも、可変長引数マクロと組み合わせればできそうだと思ってコードを考えてみた。
ところが、どうにもうまくいかない。
なお、動作確認にはclang-3.0を使用した。

まず、最初に思い付いたのが以下のコード。

#include <stdio.h>
 
// 以下の4つの関数をfunc()マクロで呼び分けたい。
void func0(void)
{
    puts("func0()");
}
 
void func1_i(int x)
{
    printf("func1_i(%d)\n", x);
}
 
void func1_d(double x)
{
    printf("func1_d(%f)\n", x);
}
 
void func2_ii(int x, int y)
{
    printf("func2_ii(%d, %d)\n", x, y);
}
 
// 引数が少ない場合の穴埋め用ダミー。
struct dummy{int n;};
 
#define func(...) (func_selector(__VA_ARGS__, (struct dummy *)0, (struct dummy *)0)(__VA_ARGS__))
#define func_selector(arg1, arg2, ...) \
  _Generic((arg1), \
  struct dummy: func0,                       /* ひとつめがない → 0引数関数 */ \
  int         : _Generic((arg2),             /* ひとつめの型がintで... */ \
                  struct dummy *: func1_i,   /* ふたつめがない → 引数の型が(int) */\
                  int           : func2_ii), /* ふたつめの型がint → 引数の型が(int, int) */ \
  double      : func1_d)                     /* ひとつめの型がdouble → 引数の型が(double)  */
 
int main()
{
    func(); // func_selectorを展開すると _Generic((), ……)となるのでエラー。
    func(1);
    func(1.0);
    func(1, 2);
    return 0;
}

この例では、引数の数が1個のものと2個のものではうまく動作するが、引数が0個のものは、「func()」→「func_selector(, (struct dummy *)0, (struct dummy *)0)」→「_Generic((), ……)」となり、「()」なんて式は書けないためコンパイルエラーになってしまう。
ではもう一段マクロを挟んで、0引数関数を特別扱いしたらいいんじゃないかと考えたのが次の例。
マクロ定義部以外は同じなので、マクロ定義部のみ記述。

// 0引数関数かそれ以外かの判別を行う
#define func_selector_1st(...) \
  _Generic((int(*)[sizeof(#__VA_ARGS__)])0, /* #__VA_ARGS__ は引数なしなら空文字列 (sizeof(...) == 1) になる */\
    int(*)[1] : func0(), /* 引数なし */\
    default: func_selector_2nd(__VA_ARGS__, (struct dummy *)0)(__VA_ARGS__)) /* 引数あり */
 
// 1引数以上の関数の判別
#define func_selector_2nd(arg1, arg2, ...) \
  _Generic((arg1), \
  int         : _Generic((arg2),           /* ひとつめの型がintで... */ \
                struct dummy *: func1_i,   /* ふたつめがない → 引数の型が(int) */\
                int           : func2_ii), /* ふたつめの型がint → 引数の型が(int, int) */ \
  double      : func1_d)                   /* ひとつめの型がdouble → 引数の型が(double)  */

func_selector_1st()で0引数とそれ以外を分けるように変更。func_selector_1st()の_Genericで選ばれなかった方は評価されないのでこれで解決、なんて思っていたが、「評価されない」と「翻訳の対象にならない」を混同していたというオチ。
結局、0引数の場合でもfunc_selector_2nd(……)が展開されてしまうため、「_Generic((), ……)」が出現するのは変わらずやっぱりコンパイルエラー。

さて、こうなるともっと発想を変えないといけなそうだけど、さてどうしたらいいものか。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

犯人は巨人ファンでA型で眼鏡をかけている -- あるハッカー

読み込み中...