Yoh2の日記: 続・C11で疑似関数オーバーロード (GCC拡張機能使用)
先日の日記で、C11を使って擬似的な関数オーバーロードを実現しようとしたが、0引数の場合にうまくいかないということを書いた。
その後GCCの拡張機能を使うと実現できることに気付いたのでここに書く。
なお、C11のみで実現する方法はまだ思い付いていない。
さて、ここで使用した拡張機能は何かというと、可変長引数マクロで、コンマの後に空の__VA_ARGS__を続けた時に__VA_ARGS__の直前のコンマを削除できるという機能。
#define MACRO(...) ほげほげ , ## __VA_ARGS__
のように、コンマと__VA_ARGS__の間に ## を入れると、「MACRO()」と書いた時に__VA_ARGS__直前のコンマが消えて、「ほげほげ」と展開される。
MACRO(a)と書いた場合はコンマが残って「ほげほげ , a」と展開される。
この拡張機能は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; // 前回の日記では中身も定義していたが不要なことに気付いたので削除。
// 引数があればその引数、なければ (struct dummy *)0 と評価される式を生成。
#define arg1_or_dummy(...) ((struct dummy *)0, ## __VA_ARGS__)
#define func(...) (func_selector_1st(__VA_ARGS__, (struct dummy *)0)(__VA_ARGS__))
#define func_selector_1st(arg1, ...) \
func_selector_2nd(arg1_or_dummy(arg1), __VA_ARGS__)
#define func_selector_2nd(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(1);
func(1.0);
func(1, 2);
return 0;
}
ここのキモとなるのが、新たに定義したマクロarg1_or_dummy。
可変長マクロとなっているが、0個か1個の引数を取ることを想定しており、「arg1_or_dummy()」と0引数にすると「((struct dummy *)0)」と展開される。
一方、「arg1_or_dummy(exp)」と引数が付くと「((struct dummy *)0, exp)」と展開され、コンマ演算子の定義により、((struct dummy *)0が捨てられて)「exp」と等価になる。
最初の引数についてこれを呼び出してやることによって、0引数のfunc呼び出しについてもめでたく正しい展開がなされて疑似オーバーロードが実現できるという寸法。
今回もclang-3.0で確認しました。
ところで、前回書き忘れたけど、clang-3.0はまだC11対応途中 (そもそもC11が正式に決まる前にリリースされたもの。オプションも-std=c1xとなっている) なので、未実装だったり規格と動作が異なったりする可能性はありますのでご注意を。_Generic周りについては規格通りだと思いますが。
続・C11で疑似関数オーバーロード (GCC拡張機能使用) More ログイン