Как добиться перегрузки функции в C?


241

Есть ли способ добиться перегрузки функции в C? Я смотрю на простые функции, которые будут перегружены, как

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

Я думаю, что нет прямого пути; Я ищу обходные пути, если таковые существуют.


6
Почему вы хотите это сделать? С не обладает полиморфными способностями. Так что foo (случайный тип) невозможен. Просто сделайте настоящие фанки foo_i, foo_ch, foo_d и т. Д.
jmucchiello

4
Вы можете пойти по злому пути, используя указатели void и идентификаторы типов.
алк

11
Я чувствую, что должен обратить внимание на тот факт, что ответ на этот вопрос изменился с тех пор, как он был задан , с новым стандартом C.
Леушенко

Ответы:


127

Есть несколько возможностей:

  1. функции стиля printf (введите в качестве аргумента)
  2. функции стиля opengl (введите имя функции)
  3. подмножество c ++ (если вы можете использовать компилятор c ++)

1
Можете ли вы объяснить или предоставить ссылки на функции стиля opengl?
FL4SOF

1
@Lazer: Вот одна простая реализация, похожая на printf.
Алексей Фрунзе

12
Нет. Printf не перегружает функцию. он использует vararg !!! И C не поддерживает перегрузку функций.
HQT

52
@hqt В ответе никогда не упоминается слово перегрузка.
kyrias

1
@kyrias Если ответ не о перегрузке, это не тот вопрос
Майкл Мрозек

233

Да!

Со времени, когда этот вопрос был задан, стандарт C (без расширений) фактически получил поддержку перегрузки функций (не операторов) благодаря добавлению _Genericключевого слова в C11. (поддерживается в GCC начиная с версии 4.9)

(Перегрузка на самом деле не является «встроенной» в способе, показанном в вопросе, но реализовать что-то, что так работает, очень просто.)

_Genericявляется оператором времени компиляции в том же семействе, что sizeofи _Alignof. Это описано в стандартном разделе 6.5.1.1. Он принимает два основных параметра: выражение (которое не будет оцениваться во время выполнения) и список ассоциаций типа / выражения, который немного похож на 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:ассоциацию для третьего случая, но это не демонстрирует, как распространить принцип на несколько аргументов. Конечным результатом является то, что вы можете использовать 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"
}

( реализация здесь ) Таким образом, прилагая некоторые усилия, вы можете уменьшить количество стандартного шаблона, чтобы он выглядел почти как язык с встроенной поддержкой перегрузки.

Кроме того, уже было возможно перегружать количество аргументов (не тип) в C99.


[1] обратите внимание, что способ, которым C оценивает типы, может вас сбить с толку. Это выберет, например, foo_intесли вы попытаетесь передать ему символьный литерал, и вам нужно немного повозиться, если вы хотите, чтобы ваши перегрузки поддерживали строковые литералы. Все же в целом довольно круто, хотя.


Исходя из вашего примера, похоже, что единственная перегружаемая функция - это макросы. Позвольте мне понять, правильно ли я понимаю: если вы хотите перегружать функции, вы просто используете препроцессор для перенаправления вызова функции на основе переданных типов данных, верно?
Ник

Увы, всякий раз, когда C11 начинает завоевывать популярность, я предполагаю, что MISRA не будет использовать эту функцию по тем же причинам, по которым они запрещают списки переменных аргументов. Я стараюсь придерживаться MISRA довольно близко в моем мире.
Ник

9
@ Ник это все перегрузка есть. Это просто неявно обрабатывается в других языках (например, вы не можете получить «указатель на перегруженную функцию» на любом языке, потому что перегрузка подразумевает несколько тел). Обратите внимание, что это не может сделать только препроцессор, это требует какой-то тип диспетчеризации; препроцессор просто меняет то, как это выглядит.
Леушенко

1
Как человек, который довольно хорошо знаком с C99 и хочет научиться делать это, это кажется слишком сложным даже для C.
Тайлер Кромптон

5
@TylerCrompton Это оценивается во время компиляции.
JAB

75

Как уже говорилось, перегрузка в том смысле, который вы имеете в виду, не поддерживается C. Обычной идиомой для решения проблемы является заставить функцию принимать теговое объединение . Это реализуется structпараметром, где structсам состоит из некоторого типа индикатора типа, такого как enum, и a 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
Почему бы вам не только сделать все whateverS на отдельные функции ( set_int, set_floatи т.д.). Затем «тегирование с типом» становится «добавить имя типа к имени функции». Версия в этом ответе включает в себя больше печатания, больше затрат времени выполнения, больше шансов на ошибки, которые не будут обнаружены во время компиляции ... Я не вижу никакого преимущества в таких действиях! 16 голосов ?!
Бен

20
Бен, за этот ответ проголосовали, потому что он отвечает на вопрос, а не просто говорит «не делай этого». Вы правы, что в C более идиоматично использовать отдельные функции, но если кто-то хочет полиморфизма в C, это хороший способ сделать это. Далее, этот ответ показывает, как вы реализуете полиморфизм во время выполнения в компиляторе или виртуальной машине: помечайте значение типом, а затем отправляйте на основе этого. Таким образом, это отличный ответ на оригинальный вопрос.
Нильс фон Барт

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
Я определенно предпочитаю 1 единственный непрерывный блок полного и работоспособного кода отбивному с нарезкой и кубиками, которое является # 1240268. Каждому свое.
Джей Тейлор

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);
}

Он выведет 0 и привет .. из printA и printB.


2
int main (int argc, char ** argv) {int a = 0; печать (а); печать ( "Hello"); возврат (EXIT_SUCCESS); } выведет 0 и привет .. из printA и printB ...
Капитан Барбосса

1
__builtin_types_compatible_p, этот компилятор GCC не специфичен?
Согартар,

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) для поддержки одной и той же операции различными типами, но вы не можете изменять поведение между разными перегрузками. Если вам нужны реальные функции, а не макросы, возможно, вы сможете вставить тип в имя и использовать вторую вставку для доступа к нему (я не пробовал).


Можете ли вы объяснить немного больше о подходе, основанном на макросах.
FL4SOF

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()) по-прежнему создает токен, но пустой. Таким образом, счетный макрос фактически возвращает 1 вместо 0 даже при пустом вызове макроса. Мы можем «легко» устранить эту проблему, если поставить __VA_ARGS__ условную запятую после , в зависимости от того, пустой список или нет:

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

Это выглядело легко, но COMMAмакрос довольно тяжелый; К счастью, эта тема уже освещена в блоге Jens Gustedt (спасибо, 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 ,

И теперь у нас все хорошо ...

Полный код в одном блоке:

/*
 * 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
Нет, если компилятор C ++ недоступен для ОС, для которой он кодирует.
Брайан

2
не только это, но он мог бы хотеть C ABI, у которого нет искажения имени в этом.
Spudd86

-3

Попробуйте объявить эти функции так, как extern "C++"если бы ваш компилятор это поддерживал, http://msdn.microsoft.com/en-us/library/s6y4zxec(VS.80).aspx


3
Это может изменить искажение имен, чтобы дать им уникальные имена (вероятно, нет), но это внезапно не даст правила разрешения перегрузки C.
Бен Фойгт

-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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.