C言語で関数のオーバーロードを実現する方法はありますか?次のような単純な関数をオーバーロードしたいと考えています。
foo (int a)
foo (char b)
foo (float c , int d)
直接的な方法はないと思います。回避策があれば探しています。
ベストアンサー1
はい!
この質問がなされて以来、標準 C (拡張機能なし) は、 C11 でキーワードが追加されたことにより、関数オーバーロード (演算子ではない) のサポートを事実上獲得しまし_Generic
た。(GCC バージョン 4.9 以降でサポートされています)
(オーバーロードは、質問に示されているような形では実際には「組み込まれている」わけではありませんが、そのように動作するものを実装するのは非常に簡単です。)
_Generic
sizeof
は、およびと同じファミリーのコンパイル時演算子です_Alignof
。これは、標準セクション 6.5.1.1 で説明されています。これは、式 (実行時には評価されません) と、ブロックに少し似た型/式関連付けリストの 2 つの主なパラメータを受け入れます。switch
式_Generic
の全体的な型を取得し、それを「切り替え」て、その型のリスト内の最終結果の式を選択します。
_Generic(1, float: 2.0,
char *: "2",
int: 2,
default: get_two_object());
上記の式は と評価されます2
。制御式の型は なのでint
、 に関連付けられた式がint
値として選択されます。実行時には、この式は何も残りません。(default
句はオプションです。これを省略して型が一致しない場合は、コンパイル エラーが発生します。)
これが関数のオーバーロードに役立つのは、C プリプロセッサによって挿入され、制御マクロに渡される引数の型に基づいて結果式を選択できるからです。したがって (C 標準からの例):
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
default: cbrt, \
float: cbrtf \
)(X)
このマクロはcbrt
、マクロへの引数の型をディスパッチし、適切な実装関数を選択し、元のマクロ引数をその関数に渡すことによって、オーバーロードされた操作を実装します。
したがって、元の例を実装するには、次のようにします。
foo_int (int a)
foo_char (char b)
foo_float_int (float c , int d)
#define foo(_1, ...) _Generic((_1), \
int: foo_int, \
char: foo_char, \
float: _Generic((FIRST(__VA_ARGS__,)), \
int: foo_float_int))(_1, __VA_ARGS__)
#define FIRST(A, ...) A
この場合、3 番目のケースに関連付けを使用することもできますが、これでは複数の引数に原則を拡張する方法が示されません。最終結果は、引数の型についてあまり気にせずにコード内でdefault:
使用できるようになります[1]。foo(...)
編集 Cosinusは、C23またはGNU拡張で動作する、複数引数オーバーロードのためのはるかにエレガントなソリューションを持っています。ただし、以下のテクニックは C11 に対して書かれたものです (C11 では実際にはこのようなことは許可されていませんでした)。
より複雑な状況、たとえば多数の引数をオーバーロードする関数や、引数の数が異なる関数の場合は、ユーティリティ マクロを使用して静的ディスパッチ構造を自動的に生成できます。
void print_ii(int a, int b) { printf("int, int\n"); }
void print_di(double a, int b) { printf("double, int\n"); }
void print_iii(int a, int b, int c) { printf("int, int, int\n"); }
void print_default(void) { printf("unknown arguments\n"); }
#define print(...) OVERLOAD(print, (__VA_ARGS__), \
(print_ii, (int, int)), \
(print_di, (double, int)), \
(print_iii, (int, int, int)) \
)
#define OVERLOAD_ARG_TYPES (int, double)
#define OVERLOAD_FUNCTIONS (print)
#include "activate-overloads.h"
int main(void) {
print(44, 47); // prints "int, int"
print(4.4, 47); // prints "double, int"
print(1, 2, 3); // prints "int, int, int"
print(""); // prints "unknown arguments"
}
(ここで実装) したがって、少し努力すれば、定型句の量を減らして、オーバーロードをネイティブにサポートする言語とほぼ同じようにすることができます。
余談として、それはすでに可能だったC99 では引数の数(型ではない)をオーバーロードします。
[1] ただし、Cの型評価の仕方によっては失敗する可能性があるので注意してください。foo_int
たとえば、文字リテラルを渡そうとすると、少しいじってみる必要があるオーバーロードで文字列リテラルをサポートしたい場合。それでも全体的にはかなりクールです。