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


240

Cで関数のオーバーロードを実現する方法はありますか?私はのようにオーバーロードされる単純な関数を探しています

foo (int a)  
foo (char b)  
foo (float c , int d)

簡単な方法はないと思います。回避策がある場合は、それを探しています。


6
なぜこれをしたいのですか?Cにはポリモーフィック機能はありません。つまり、foo(ランダム型)は不可能です。実際のfooc、foo_i、foo_ch、foo_dなどを作成するだけ
jmucchiello 2009年

4
voidポインターとタイプIDを使用すると、邪悪な方法をとることができます。
アルク

11
この質問への回答が、最初に尋ねられたときから変更されたという事実に注意を向けるべきだと思います。新しいC標準です。
ロイセンコ2014

回答:


127

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

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

1
openglスタイルの関数の説明やリンクを提供できますか?
FL4SOF 2009年

1
@Lazer:次に、単純なprintfのような関数の実装を示します。
Alexey Frunze

12
いいえ。printfは関数のオーバーロードではありません。それは可変引数を使用します!!! また、Cは関数のオーバーロードをサポートしていません。
hqt 2012

52
@hqt答えには、オーバーロードという言葉は含まれていません。
キリアス2013年

1
@kyrias答えは、それは間違っている問題についてだの過負荷ではない場合には
マイケルMrozek

233

はい!

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

(オーバーロードは、質問で示された方法で本当に「組み込み」ではありませんが、そのように機能するものを実装するのは非常に簡単です。)

_Genericsizeofおよびと同じファミリーのコンパイル時演算子_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

この場合default:、3番目のケースの関連付けを使用することもできますが、それは原則を複数の引数に拡張する方法を示していません。最終結果はfoo(...)、引数の型を心配することなく(much [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"
}

ここでの実装)したがって、いくらかの労力で、ボイラープレートの量を減らして、オーバーロードをネイティブでサポートする言語によく似たものにすることができます。

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


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


あなたの例に基づいて、オーバーロードされているのはマクロのような関数だけであるように見えます。私が正しく理解しているかどうかを見てみましょう:関数をオーバーロードしたい場合は、プリプロセッサを使用して、渡されたデータ型に基づいて関数呼び出しを迂回させますか?
Nick

悲しいかな、C11がキャッチし始めるときはいつでも、可変引数リストを禁止するのと同じ理由で、MISRAはこの機能を採用しないと思います。私の世界ではMISRAにかなり近づこうとしています。
Nick

9
@Nickはすべてオーバーロードです。他の言語では暗黙的に処理されます(たとえば、オーバーロードは複数の本体を意味するため、どの言語でも「オーバーロードされた関数へのポインター」を実際に取得することはできません)。これはプリプロセッサだけでは実行できないことに注意してください。ある種の型ディスパッチが必要です。プリプロセッサは見た目を変えるだけです。
Leushenko、2015

1
C99とかなり精通していると、これを行う方法を学習したい誰かのように、これはさえC.のために、過度に複雑なようだ
タイラークロンプトン

5
@TylerCromptonコンパイル時に評価されます。
JAB 2017

75

すでに述べたように、という意味でのオーバーロードはCではサポートされていません。問題を解決するための一般的なイディオムは、関数にタグ付き共用体を受け入れさせることです。これは、structパラメータによって実装されます。パラメータstruct自体は、のようなある種のタイプインジケータと、さまざまなタイプの値のenuma 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); 
}

22
あなただけのすべてのことはないだろうなぜwhatever(別々の機能にSをset_intset_floatなど)。すると「型のタグ付け」が「型名を関数名に追加」になります。この回答のバージョンには、より多くのタイピング、より多くの実行時コスト、コンパイル時にキャッチされないエラーの可能性が多く含まれています... この方法で行うことには何の利点もありません!16票?!
Ben

20
ベン、この答えは「それをしないでください」と言うのではなく、質問答えるために賛成です。Cでは別の関数を使用する方が慣用的ですが、Cでポリモーフィズムが必要な場合は、これが適切な方法です。さらに、この回答は、コンパイラーまたはVMにランタイムポリモーフィズムを実装する方法を示しています。値に型のタグを付け、それに基づいてディスパッチします。したがって、元の質問に対する優れた回答です。
Nils von Barth 2014年

20

これが、Cで関数のオーバーロードを示していることがわかり、最も簡潔な例です。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int addi(int a, int b) {
    return a + b;
}

char *adds(char *a, char *b) {
    char *res = malloc(strlen(a) + strlen(b) + 1);
    strcpy(res, a);
    strcat(res, b);
    return res;
}

#define add(a, b) _Generic(a, int: addi, char*: adds)(a, b)

int main(void) {
    int a = 1, b = 2;
    printf("%d\n", add(a, b)); // 3

    char *c = "hello ", *d = "world";
    printf("%s\n", add(c, d)); // hello world

    return 0;
}

https://gist.github.com/barosl/e0af4a92b2b8cabd05a7


1
私はこれがstackoverflow.com/a/25026358/1240268の精神の複製だと思います(ただし説明は少ないです)。
アンディヘイデン

1
#1240268のスライスとダイシングのチョップよりも、完全で実行可能なコードの1つの連続したブロックを確実に好みます。それぞれ独自に。
Jay Taylor

1
私は彼らが何をしていてなぜ彼らが働くのかを説明する答えを好む。これはどちらも行いません。「私が今まで見た中で最高:」は説明ではありません。
underscore_d

19

コンパイラがgccで、新しいオーバーロードを追加するたびに手動で更新することを気にしない場合は、マクロマジックを実行して、呼び出し元に関して必要な結果を得ることができますが、書くのはあまり良くありません...しかし、それは可能です

__builtin_types_compatible_pを確認し、それを使用して次のようなマクロを定義します

#define foo(a) \
((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)

しかし、いや、嫌なだけ

編集: C1Xは、次のような型のジェネリック式のサポートを取得します。

#define cbrt(X) _Generic((X), long double: cbrtl, \
                              default: cbrt, \
                              float: cbrtf)(X)

13

はい、そうです。

ここに例を示します:

void printA(int a){
printf("Hello world from printA : %d\n",a);
}

void printB(const char *buff){
printf("Hello world from printB : %s\n",buff);
}

#define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0 
#define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N
#define _Num_ARGS_(...) __VA_ARG_N(__VA_ARGS__) 
#define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1) 
#define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args)>t)
#define CHECK_ARGS_MIN_LIMIT(t) if(NUM_ARGS(args) 
#define print(x , args ...) \
CHECK_ARGS_MIN_LIMIT(1) printf("error");fflush(stdout); \
CHECK_ARGS_MAX_LIMIT(4) printf("error");fflush(stdout); \
({ \
if (__builtin_types_compatible_p (typeof (x), int)) \
printA(x, ##args); \
else \
printB (x,##args); \
})

int main(int argc, char** argv) {
    int a=0;
    print(a);
    print("hello");
    return (EXIT_SUCCESS);
}

printAとprintBから0とhello ..を出力します。


2
int main(int argc、char ** argv){int a = 0; print(a); print( "hello"); 戻り値(EXIT_SUCCESS); }は0を出力し、hello ..をprintAとprintBから出力します...
キャプテンBarbossa

1
__builtin_types_compatible_p、そのGCCコンパイラ固有ではないですか?
ソガルタール2013年

11

次のアプローチはa2800276のアプローチに似ていますが、いくつかのC99マクロマジックが追加されています。

// we need `size_t`
#include <stddef.h>

// argument types to accept
enum sum_arg_types { SUM_LONG, SUM_ULONG, SUM_DOUBLE };

// a structure to hold an argument
struct sum_arg
{
    enum sum_arg_types type;
    union
    {
        long as_long;
        unsigned long as_ulong;
        double as_double;
    } value;
};

// determine an array's size
#define count(ARRAY) ((sizeof (ARRAY))/(sizeof *(ARRAY)))

// this is how our function will be called
#define sum(...) _sum(count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__))

// create an array of `struct sum_arg`
#define sum_args(...) ((struct sum_arg []){ __VA_ARGS__ })

// create initializers for the arguments
#define sum_long(VALUE) { SUM_LONG, { .as_long = (VALUE) } }
#define sum_ulong(VALUE) { SUM_ULONG, { .as_ulong = (VALUE) } }
#define sum_double(VALUE) { SUM_DOUBLE, { .as_double = (VALUE) } }

// our polymorphic function
long double _sum(size_t count, struct sum_arg * args)
{
    long double value = 0;

    for(size_t i = 0; i < count; ++i)
    {
        switch(args[i].type)
        {
            case SUM_LONG:
            value += args[i].value.as_long;
            break;

            case SUM_ULONG:
            value += args[i].value.as_ulong;
            break;

            case SUM_DOUBLE:
            value += args[i].value.as_double;
            break;
        }
    }

    return value;
}

// let's see if it works

#include <stdio.h>

int main()
{
    unsigned long foo = -1;
    long double value = sum(sum_long(42), sum_ulong(foo), sum_double(1e10));
    printf("%Le\n", value);
    return 0;
}

11

これはまったく役に立たないかもしれませんが、clangを使用している場合は、overloadable属性を使用できます-これはCとしてコンパイルする場合でも機能します

http://clang.llvm.org/docs/AttributeReference.html#overloadable

ヘッダ

extern void DecodeImageNow(CGImageRef image, CGContextRef usingContext) __attribute__((overloadable));
extern void DecodeImageNow(CGImageRef image) __attribute__((overloadable));

実装

void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }

10

あなたが意味する意味で-いいえ、あなたはできません。

次のva_argような関数を宣言できます

void my_func(char* format, ...);

ですが、変数の数とその型に関するある種の情報を最初の引数で渡す必要があります—のようにprintf()


6

通常、タイプが名前に追加または追加されることを示すイボ。場合によっては、マクロを回避することもできますが、それはむしろ、実行しようとしていることに依存します。Cにはポリモーフィズムはなく、強制のみです。

単純な一般的な操作は、マクロで実行できます。

#define max(x,y) ((x)>(y)?(x):(y))

コンパイラがtypeofをサポートしている場合、より複雑な操作をマクロに入れることができます。その後、シンボルfoo(x)を使用して、同じ操作のさまざまなタイプをサポートできますが、さまざまなオーバーロード間で動作を変えることはできません。マクロではなく実際の関数が必要な場合は、タイプを名前に貼り付け、2番目の貼り付けを使用してアクセスすることができます(私は試していません)。


マクロベースのアプローチについてもう少し説明してください。
FL4SOF 2009年

4

ロイシェンコの答えは本当にクールです-単に:foo例はGCCでコンパイルされません。これはで失敗しfoo(7)FIRSTマクロと実際の関数呼び出し((_1, __VA_ARGS__)、余分なコンマが残っています)につまずきます。 、 といったfoo(double)

そのため、私は答えをもう少し詳しく説明することにしました。これには、無効なオーバーロード(foo(void)かなりの問題が発生しました...)。

アイデアは次のとおりです:異なるマクロで複数のジェネリックを定義し、引数の数に応じて正しいジェネリックを選択しましょう!

この答えに基づいて、引数の数は非常に簡単です:

#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)

#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y

それはいいですね、SELECT_1またはSELECT_2(または必要に応じてさらに引数を)解決するので、適切な定義が必要です。

#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1),    \
        int: foo_int,                   \
        char: foo_char,                 \
        double: foo_double              \
)
#define SELECT_2(_1, _2) _Generic((_1), \
        double: _Generic((_2),          \
                int: foo_double_int     \
        )                               \
)

はい、すでにvoidオーバーロードを追加しました。ただし、これは実際にはC標準ではカバーされていません。これにより、空の可変引数が許可されません。つまり、コンパイラー拡張に依存します。ます。

最初は、空のマクロ呼び出し(foo())によってトークンが生成されますが、空のトークンが生成されます。そのため、空のマクロ呼び出しでも、カウントマクロは実際には0ではなく1を返します。リストが空かどうかに応じて、__VA_ARGS__ 条件付きでの後にコンマを配置すると、この問題を「簡単に」解消できます。

#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)

それ簡単に見えましたが、COMMAマクロはかなり重いものです。幸い、このトピックはJens Gustedtのブログで既に取り上げられています(Jensに感謝)。基本的なトリックは、括弧が続かないと関数マクロが展開されないことです。詳細については、Jensのブログを参照してください...必要に応じてマクロを少し変更するだけです(短い名前を使用します)簡潔にするために、より少ない引数)。

#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, _3, N, ...) N
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0)

#define SET_COMMA(...) ,

#define COMMA(...) SELECT_COMMA             \
(                                           \
        HAS_COMMA(__VA_ARGS__),             \
        HAS_COMMA(__VA_ARGS__ ()),          \
        HAS_COMMA(SET_COMMA __VA_ARGS__),   \
        HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)

#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3

#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
// ... (all others with comma)
#define COMMA_1111 ,

そして今、私たちは元気です...

1つのブロック内の完全なコード:

/*
 * demo.c
 *
 *  Created on: 2017-09-14
 *      Author: sboehler
 */

#include <stdio.h>

void foo_void(void)
{
    puts("void");
}
void foo_int(int c)
{
    printf("int: %d\n", c);
}
void foo_char(char c)
{
    printf("char: %c\n", c);
}
void foo_double(double c)
{
    printf("double: %.2f\n", c);
}
void foo_double_int(double c, int d)
{
    printf("double: %.2f, int: %d\n", c, d);
}

#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)

#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y

#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1), \
        int: foo_int,                \
        char: foo_char,              \
        double: foo_double           \
)
#define SELECT_2(_1, _2) _Generic((_1), \
        double: _Generic((_2),          \
                int: foo_double_int     \
        )                               \
)

#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, N, ...) N

#define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0)
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0)

#define SET_COMMA(...) ,

#define COMMA(...) SELECT_COMMA             \
(                                           \
        HAS_COMMA(__VA_ARGS__),             \
        HAS_COMMA(__VA_ARGS__ ()),          \
        HAS_COMMA(SET_COMMA __VA_ARGS__),   \
        HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)

#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3

#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
#define COMMA_0011 ,
#define COMMA_0100 ,
#define COMMA_0101 ,
#define COMMA_0110 ,
#define COMMA_0111 ,
#define COMMA_1000 ,
#define COMMA_1001 ,
#define COMMA_1010 ,
#define COMMA_1011 ,
#define COMMA_1100 ,
#define COMMA_1101 ,
#define COMMA_1110 ,
#define COMMA_1111 ,

int main(int argc, char** argv)
{
    foo();
    foo(7);
    foo(10.12);
    foo(12.10, 7);
    foo((char)'s');

    return 0;
}

1

C ++だけを使用して、これ以外のすべてのC ++機能を使用することはできませんか?

それでも厳密なCではない場合は、代わりに可変関数をお勧めします


3
彼がコーディングしているOSでC ++コンパイラが利用できない場合はそうではありません。
ブライアン

2
それだけでなく、名前がマングルされていないC ABIが必要になる場合もあります。
Spudd86

-3

extern "C++"コンパイラがこれをサポートしているかのように、これらの関数を宣言してください。http://msdn.microsoft.com/en-us/library/s6y4zxec(VS.80).aspx


3
これにより、名前のマングリングが変更されて一意の名前が付けられる可能性がありますが(おそらくそうではありません)、Cのオーバーロード解決ルールが突然与えられることはありません。
Ben Voigt 2013年

-4

以下のコードが関数のオーバーロードを理解するのに役立つことを願っています

#include <stdio.h>
#include<stdarg.h>

int fun(int a, ...);
int main(int argc, char *argv[]){
   fun(1,10);
   fun(2,"cquestionbank");
   return 0;
}
int fun(int a, ...){
  va_list vl;
  va_start(vl,a);

  if(a==1)
      printf("%d",va_arg(vl,int));
   else
      printf("\n%s",va_arg(vl,char *));
}

2
答えは、それが何をしているか、なぜそれが機能するかを説明する必要があります。そうでない場合、それはどのようにして誰かが何かを理解するのに役立ちますか?
underscore_d

ここではオーバーロードはありません。
メルポメン

va_endが呼び出されなかった
user2262111
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.