2014年6月25日水曜日

[C11] _Generic を使ってみた

gcc 4.9 で _Generic が使えるようになったので、使ってみました。
(clang では結構前から使えたみたいですね)

_Generic とは?
_Generic(制御式, 型名1: 式1, 型名2: 式2…)
_Generic は与えられた型によって評価される式を決定する式です。
これはコンパイル時に決定されます。

例えば、以下のように使用できます。
#define abs(x) _Generic((x), long: labs, double: fabs, default: abs)(x)

_Generic 自体については他のサイトも参考にしてください。

やろうとしてできなかったこと

#define printf_dec_format(x) _Generic((x), \
    char: "%c", \
    signed char: "%hhd", \
    unsigned char: "%hhu", \
    signed short: "%hd", \
    unsigned short: "%hu", \
    signed int: "%d", \
    unsigned int: "%u", \
    long int: "%ld", \
    unsigned long int: "%lu", \
    long long int: "%lld", \
    unsigned long long int: "%llu", \
    float: "%f", \
    double: "%f", \
    long double: "%Lf", \
    char *: "%s", \
    void *: "%p")
Rob's Programming Blog: C11 - Generic Selections

#define Message(a, b) \
    printf("a = " printf_dec_format(a) "\nb = " \
         printf_dec_format(b) "\n", a, b)

文字列リテラルは以下のように途切れていても1つの文字列リテラルとして扱われるので、それを期待したのですがダメでした。
printf( "aaaa" "bbbb" "\n");

メンドクサイですが、このように書く必要があるようです。
#define PRINTF_FORMAT_PARAM(prefix, x, postfix) _Generic((x) \
    , char : prefix "%c" postfix                \
    , signed char: prefix "%hhd" postfix        \
    , unsigned char: prefix "%hhu" postfix      \
    , signed short: prefix "%hd" postfix        \
    , unsigned short: prefix "%hu" postfix      \
    , signed int: prefix "%d" postfix           \
    , unsigned int: prefix "%u" postfix         \
    , signed long: prefix "%ld" postfix         \
    , unsigned long: prefix "%lu" postfix       \
    , char* : prefix "%s" postfix               \
    , void* : prefix "%p" postfix               \
    , default: prefix "%p" postfix              \
    )

#define PRINTF_FORMAT_PARAM2(str0, x, str1, y, str2) _Generic((x)    \
    , char : PRINTF_FORMAT_PARAM(y, str0 "%c" str1, str2)            \
    , signed char: PRINTF_FORMAT_PARAM(y, str0  "%hhd" str1, str2)   \
    , unsigned char: PRINTF_FORMAT_PARAM(y, str0  "%hhu" str1, str2) \
    , signed short: PRINTF_FORMAT_PARAM(y, str0  "%hd" str1, str2)   \
    , unsigned short: PRINTF_FORMAT_PARAM(y, str0  "%hu" str1, str2) \
    , signed int: PRINTF_FORMAT_PARAM(y, str0  "%d" str1, str2)      \
    , unsigned int: PRINTF_FORMAT_PARAM(y, str0  "%u" str1, str2)    \
    , signed long: PRINTF_FORMAT_PARAM(y, str0  "%ld" str1, str2)    \
    , unsigned long: PRINTF_FORMAT_PARAM(y, str0  "%lu" str1, str2)  \
    , char* : PRINTF_FORMAT_PARAM(y, str0  "%s" str1, str2)          \
    , void* : PRINTF_FORMAT_PARAM(y, str0  "%p" str1, str2)          \
    , default: PRINTF_FORMAT_PARAM(y, str0  "%p" str1, str2)         \
    )

#define Message(a, b) \
    printf(PRINTF_FORMAT_PARAM2("a = ", a , "\nb = ", b, "\n"), a, b)
もっと効率的な書き方があればコメント下さい。

指定した名前が型であるかどうか調べるマクロ


というわけで、C11 でも書いてみた。

#include <stdio.h>

#define IS_TYPE_NAME(name) _Generic((void (*)(int (name)))0, void (*)(int):0, default: 1)

typedef float f;
int main()
{
    printf("%d\n", IS_TYPE_NAME(int));
    printf("%d\n", IS_TYPE_NAME(f));
    printf("%d\n", IS_TYPE_NAME(main));
    printf("%d\n", IS_TYPE_NAME(aaa));
    return 0;
}

1
1
0
0

なにかに使えそうな気がする。

iutest_c での利用
はじめに iutest_c を紹介しておきます。
iutest_c は「C言語のテスティングフレームワーク」です。C++テスティングフレームワークの iutest もありますが、こちらはC言語で書かれています。C++ のと比べると非常に貧弱なフレームワークですが、軽量であることは間違いありません。(小さなデバイスなどに載せることを想定していますが、あまり検証もできていませんし、そこに載せることを考えると軽量とは言い難いかも)

printer の改善
貧弱な点の一つとして、printer がビミョーでした。
アサーションに失敗した場合、期待値と実値を出力します。この時の出力が値ではなくメモリダンプを出力します。
IUTEST(Test, Sample)
{
    int zero=0;
    IUTEST_EXPECT_NE(zero, zero);
}
sample.c(16):error: Expected of :
zero != zero
Actual: 0x00000000 vs 0x00000000

ビミョー過ぎて説明するのもアレなんですが、一番ダメなところとして lvalue しか扱えないところです。
IUTEST_EXPECT_NE(0, 0); と書けません。

そこで _Generic!!

前述した printf_dec_format のように、型ごとに出力をしてあげることで lvalue 制限を取り払い、わかりやすい出力を実現しました。

IUTEST(Test, Sample)
{
    IUTEST_EXPECT_NE(0, 0);
    float fa = 2.0f/2;
    IUTEST_EXPECT_FLOAT_EQ(1.1f, fa);
}
sample.c:15:error: Expected of :
zero != zero
Actual: 0 vs 0
sample.c:17:error: Value of : fa
Actual: 1.000000
Expected: 1.1f
Which is: 1.100000



ネタ切れ
このブログを書きつつ、_Generic の利用方法を考えていたのですが、ネタ切れです。

_Generic 使ってもっと何かできるんじゃないかと思っていたのですが、なかなかうまくいかず。。。
例えば、decltype とか書けるんじゃないか?と思ったんですが、式しか扱えないのでダメでした。
関数オーバーロードの代用として使えるので、それで…と思ったんですが使いドコロがなかった。
IS_TYPE_NAME 使って何かできそうな気はするんですがねぇ…

まとめ
_Generic 便利だと思いますが、どの位需要があるんでしょうね?
C11 使うことができるんなら、C++ 使えばいいんじゃない?と思ってしまいます。

とはいえ、_Generic は便利です。iutest_c でも今後も使っていこうと思います。

0 件のコメント:

コメントを投稿