C言語アドベントカレンダー の 12/18 が空いていたので、あわてて小さな記事を書いてみた。
正直いって、仕事で C11 を使ったことはないし、趣味で書くことも殆どない。
でも、_Generic
は面白そうだと思っていたので軽く調べてみたら、罠に気づいたので記しておく。
まずはソースコード。
#include <stdio.h>
static inline void
show_int( int x ){ printf( "int: %d\n", x ); }
static inline void
show_str( char const * x ){ printf( "str: %s\n", x ); }
static inline void
show_char( char x ){ printf( "char: %c\n", x ); }
static inline void
show_bool( _Bool x ){ printf( "bool: %s\n", (x ? "true" : "false" )); }
#define show(X) _Generic((X), \
int: show_int, \
char *: show_str, \
char: show_char )(X)
int main(){
show( 123 ); //=> int: 123
show( "foo" ); //=> str: foo (合法?)
show( 'X' ); //=> int:88 ( 'X' は、char ではなくて int )
show( "X"[0] ); //=> char: X ( "X"[0] なら char になる )
show( 0==0 ); //=> int: 1 ( 0==0 は、_Bool ではなくて int )
enum{ hoge = 456 };
show( hoge ); // int: 456 ( 意外と int になる。合法? )
struct S{ int i:2; char ch:7 };
struct S s={1,33};
show( s.i ); // clang だと「int: 1」。gcc-8 だとエラー
show( s.ch ); // clang だと「char: !」。gcc-8 だとエラー
return 0;
}
確認は、clang と gcc で行った。バージョンは下表の通り:
コンパイラ | バージョン |
---|---|
clang | Apple LLVM version 10.0.0 (clang-1000.11.45.5) |
gcc | gcc-8 (Homebrew GCC 8.2.0) 8.2.0 |
いずれも macOS Mojave 上。
文字列
"foo"
は char *
ではなく char[4]
だと思うんだけど、char *
を選択してくれる。便利だとは思うけど、正しいのかどうかはよくわからない。
詳しくは コメント参照。
文字
'X'
は、C言語では char
ではなく、int
である。だから show('X')
は show_int
を選ぶ。show_char
にするためには show((char)'X')
などとする必要がある。
比較演算
比較演算の結果は _Bool
ではなく、int
になる。だから、show(0==0)
は、show_int
を選ぶ。show_bool
にするためには show((_Bool)(0==0))
などとする必要がある。
enum
enum{ hoge=456 };
の hoge
を渡すと意外とエラーにならず、 show_int
が選択される。合法なのかなぁ。
ビットフィールド
ビットフィールドを渡したときの動作は clang と gcc-8 で異なっている。
clang は int i:2
で定義されているものは int
に。 char ch:7
で定義されているものは char
に割り振られる。正しそうな感じ。
gcc-8 の場合、show( s.i )
の行は
'_Generic' selector of type 'signed char:2' is not compatible with any association
というエラーに。
show( s.ch )
の行は
'_Generic' selector of type 'signed char:7' is not compatible with any association
というエラーになる。
両方とも、いやそんな型書いてないよ、という気分になる。あんまり正しそうな気がしない。clang の勝ちかなぁ。
そんなわけで
そんなわけで、もう時間もないのでこんなところで。
_Generic
は割と楽しそうだと思うけど、型に無頓着な言語仕様との相性が微妙に悪くて心配な感じ。二項変換との組み合わせとか、16進数が int
になるのか unsigned int
になるのかとか、難しいよね。
多重 _Generic
とかをキメてへんなライブラリを作りたいような気もするけど、そもそも C11 を書く機会がないんだよなぁ。