C言語への感謝の正拳突き 今日は14日目です!
C言語のプロダクトといえば、C89とかC99でコンパイルされているものが大半だと思います。
C11使っているところどこだよ。。C11使うくらいならModernなC++使うのでは!?
なんてことを思ってしまうわけですが、
C11自体はatomicとかmulti threadとかが標準機能として用意されたらしく、その辺の機能は使いやすくなっているようです。
C11で追加された機能とか一切使ったことないし、これからも使う予定がなさそうなきがしているのですが、
generic-selectionという非常に面白そうな機能があったので、使い方を調べて動かしてみました。
概要
C11のgeneric-selectionの豊富なサンプルや実用的な使い方はこれ読めば大体分かるのではないでしょうか。
2引数の関数に対して_Genericを2重に呼び出して特殊化するサンプルは素晴らしいです。
http://www.robertgamble.net/2012/01/c11-generic-selections.html
generic-selectionのNGケースはこちらを参照ください。
https://llvm.org/svn/llvm-project/cfe/trunk/test/Sema/generic-selection.c
サンプル
試されている人は、だいたい関数オーバーロードみたいな使い方が多いようで、
私もそんな使い方でサンプルをかいてみました。
#include <stdio.h>
#include <tgmath.h>
#include <stdint.h>
#include <stdlib.h>
typedef struct {
void *user_data;
} a_t;
#define GEN_TY_LIST(method, G) \
G(method, uint32_t) \
G(method, uint8_t) \
G(method, a_t) \
#define GEN_ASSOC(method, ty) \
,ty* : method##_##ty \
#define generic_init(arg) \
_Generic(arg \
GEN_TY_LIST(init, GEN_ASSOC) \
)(arg)
#define generic_alloc(arg) \
_Generic(arg \
GEN_TY_LIST(alloc, GEN_ASSOC) \
)()
void init_uint32_t(uint32_t *arg) {
printf("callee init_uint32_t\n");
*arg = 0;
}
void init_uint8_t(uint8_t *arg) {
printf("callee init_uint8_t\n");
*arg = 0;
}
void init_a_t(a_t *arg) {
printf("callee init_a_t\n");
arg->user_data = NULL;
}
uint32_t * alloc_uint32_t() {
printf("callee alloc_uint32_t\n");
return malloc(sizeof(uint32_t));
}
uint8_t * alloc_uint8_t() {
printf("callee alloc_uint8_t\n");
return malloc(sizeof(uint8_t));
}
uint8_t * alloc_a_t() {
printf("callee alloc_a_t\n");
return malloc(sizeof(a_t));
}
int main() {
uint32_t* ap = generic_alloc(ap);
a_t* p = generic_alloc(p);
uint32_t a;
generic_init(&a);
/* ifdefを外すと、 assoc_typeのcompatible errorが返る */
#if 0
{
uint64_t b;
generic_init(&b);
}
#endif
return 0;
}
macroの引数に与えたobjectの型を参照して、適切な関数を呼び出してあげるようなサンプルです。
独自の構造体なんかを書いたときに、init,destroy,alloc,free,pprint系の関数を量産しやすいため、
それらの呼び出しを簡略化する際に使えそうと思って、サンプルを作成してみました。
こういう感じに簡略化する場合、必ず
(1) 関数の実装を定義する
(2) _Genericに登録する
は必須のようですね。
未定義や未登録の型を引数に呼び出すと、コンパイルエラーになってくれるのは嬉しいところです。
#include <stdio.h>
#include <tgmath.h>
#include <stdint.h>
typedef int32_t (cb_int32_t)(int32_t arg);
typedef int8_t (cb_int8_t)(int8_t arg);
typedef void (cb_void)(void);
#define ty2params(arg) \
_Generic(arg, \
cb_int32_t* : 1, /* *を外すと cb_int32_tはgeneric association not an object type */ \
cb_int8_t* : 2, \
cb_void* : 0 \
)
int32_t func(int32_t a) {
return 0;
}
int main() {
cb_int32_t *fp = func;
int32_t ret = ty2params(fp);
int32_t ret2 = ty2params(&main);
printf("%d\n", ret);
printf("%d\n", ret2);
return 0;
}
typedefした型、独自定義のstruct、関数ポインタの型の判別も特に問題ないみたいです。
実用的な使いどころが難しいですが、
void*で型情報を握りつぶされる手前で、型チェックすることはできるかもしれません。
あと、1点だるいなーと思ったところは、
int a[1];
_Generic(a, ...)
と指定した場合、aはint * ではなく、int[1]として解釈され、int * とは異なる型とみなされる点ですね。。
まとめ
generic-selection、関数オーバーロードみたいな使い方が多いみたいです。
generic-selectionはコンパイル時に型が静的に決定されている場合に、macroで型のswitchを簡単に書けるみたいな使用感です。
そのため、実行時の型情報を参照したりするケースと組み合わせるのは難しいと感じました。