Cで関数のオーバーロードを実現するには?

はい!

この質問が出されて以来、標準 C (拡張機能なし) は効果的に 獲得 されました。 _Generic の追加による関数のオーバーロード (演算子ではない) のサポート C11 のキーワード。 (バージョン 4.9 以降の GCC でサポートされています)

(過負荷は、質問に示されているように真に「組み込み」ではありませんが、そのように機能するものを実装するのは非常に簡単です。)

_Generic sizeof と同じファミリーのコンパイル時演算子です そして _Alignof .これは、標準セクション 6.5.1.1 で説明されています。式 (実行時に評価されない) と、switch に少し似た型/式の関連付けリストの 2 つの主要なパラメーターを受け入れます。 ブロック。 _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

この場合、default: を使用できます。 3 番目のケースの関連付けですが、それは原則を複数の引数に拡張する方法を示していません。最終結果は、 foo(...) を使用できることです 引数の型について (多く[1]) 心配することなく、コード内で使用できます。

より複雑な状況の場合。関数がより多くの引数またはさまざまな数の引数をオーバーロードする場合、ユーティリティ マクロを使用して静的ディスパッチ構造を自動的に生成できます。

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"
}

(実装はこちら) したがって、ある程度の努力をすれば、ボイラープレートの量を減らして、オーバーロードのネイティブ サポートを備えた言語とほとんど同じにすることができます。

余談ですが、number でオーバーロードすることはすでに可能でした。 C99 の引数 (型ではない) の。

[1] ただし、C が型を評価する方法にはつまずく可能性があることに注意してください。これは foo_int を選択します たとえば、文字リテラルを渡そうとすると、オーバーロードで文字列リテラルをサポートする場合は、少し混乱する必要があります。それでも全体的にかなりクールです.


いくつかの可能性があります:

<オール>
  • printf スタイルの関数 (引数として型)
  • opengl スタイルの関数 (関数名を入力)
  • c++ の c サブセット (c++ コンパイラを使用できる場合)

  • 既に述べたように、あなたが意味する意味でのオーバーロードは C ではサポートされていません。問題を解決するための一般的なイディオムは、関数がタグ付き共用体を受け入れるようにすることです。これは struct によって実装されています パラメータ、ここで struct それ自体は、 enum などのある種の型インジケータで構成されています 、および union さまざまな種類の値の。例:

    #include <stdio.h>
    
    typedef enum {
        T_INT,
        T_FLOAT,
        T_CHAR,
    } my_type;
    
    typedef struct {
        my_type type;
        union {
            int a; 
            float b; 
            char c;
        } my_union;
    } my_struct;
    
    void set_overload (my_struct *whatever) 
    {
        switch (whatever->type) 
        {
            case T_INT:
                whatever->my_union.a = 1;
                break;
            case T_FLOAT:
                whatever->my_union.b = 2.0;
                break;
            case T_CHAR:
                whatever->my_union.c = '3';
        }
    }
    
    void printf_overload (my_struct *whatever) {
        switch (whatever->type) 
        {
            case T_INT:
                printf("%d\n", whatever->my_union.a);
                break;
            case T_FLOAT:
                printf("%f\n", whatever->my_union.b);
                break;
            case T_CHAR:
                printf("%c\n", whatever->my_union.c);
                break;
        }
    
    }
    
    int main (int argc, char* argv[])
    {
        my_struct s;
    
        s.type=T_INT;
        set_overload(&s);
        printf_overload(&s);
    
        s.type=T_FLOAT;
        set_overload(&s);
        printf_overload(&s);
    
        s.type=T_CHAR;
        set_overload(&s);
        printf_overload(&s); 
    }