W.I.S. Laboratory
menu-bar

C


C言語の_Genericで関数のオーバーロードをやってみる

Cは関数のオーバーロードができない。
オーバーロードとは、引数の型や個数が異なっていれば同名関数を複数定義できる機能のことだ。
例えばC++だと関数のオーバロードができる。
C++は関数名が同じでも関数のシグネチャ(関数名、関数の型、引数の型や個数などをすべて含めた一式)が違っていれば、違う関数だと認識するわけだ。
引き換えCの場合は、関数名のみで違う関数かどうかを判別しているので、シグネチャが異なっていても同名関数を定義するとエラーになってしまうため、関数のオーバーロードはできない。
しかしC99から拡張された機能の中には、予約済み識別子を使って追加された「_Generic」というものがあり、これと関数型マクロを組み合わせて使うと関数のオーバーロードっぽいことができるようになった。
機能の名前が「ジェネリック」なので、いかにもジェネリック関数(引数がどんな型でもOKな関数)が作れそうなネーミングなのだが、そうではないようだ。

ちなみに_Genericとはどんなものなのかというと、与えられた式の型に応じてその場に展開するコードを変化させることができるという、マクロの拡張のような機能だ。
関数のように記述し、第一引数に変数やリテラルを書き、第ニ引数以降に「型名: 展開するコード」という具合に書いていく。
例えば、下の例でいうと x はchar型の変数だ。
これに対して_Genericを使うと、「char:」のすぐ右にある「"This is char"」が展開され、画面には「This is char」と表示される。

_Generic部分は与えられた式の型に応じてコード展開するので、コンパイル時には「puts("This is char")」となっているというわけだ。
変数 x をintにすれば「This is int」が表示され、char型ポインタにすれば「This is string」と表示される、という具合になる。
ただこのような使い方では意味をなさないので、下のように関数型マクロと組み合わせることがほとんどだ。

このように書くと、変数「var」の型に応じて展開される関数名が変わるわけだ。
コード中の「putvar」は関数のように見えるが、実際は関数型マクロなので、この部分が丸っと型に応じた関数名に置き換わる。
結果、まるでCで関数のオーバーロードができているかのように振る舞うコードになる・・という仕掛けだ。
ただ、Cなのであまり期待したとおりに振り分けてくれないことがある。
_Genericに渡すものが変数であれば100%期待したとおりに振り分けられるが、リテラルの場合はそうでもない。

なぜかというと、Cのリテラルは意外なほどにint型になることが多いからだ。
例えば、下のような例はint型として評価される。

'A'
1 == 1

C++なら、'A' はunsigned char型、1 == 1 はbool型として評価されるのだが、Cはどちらもint型として評価される。
なので、_Genericで処理を振り分けたいときは、上の例のようにキャストを明示しないとint型の処理に飛んでしまうことがあるので注意が必要だ。


[ 戻る ]
saluteweb