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

217

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

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

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

  • 4
    Зачем тебе это делать? С не обладает полиморфными способностями. Так что foo (случайный тип) невозможен. Просто создайте настоящие функции foo_i, foo_ch, foo_d и т. Д.
  • 3
    Вы можете пойти по злому пути, используя указатели void и идентификаторы типов.
Показать ещё 1 комментарий
Теги:
function-overloading

14 ответов

108
Лучший ответ

Существует несколько возможностей:

  • функции printf style (введите в качестве аргумента)
  • функции стиля opengl (введите имя функции)
  • c подмножество С++ (если вы можете использовать компилятор С++)
  • 1
    Можете ли вы объяснить или предоставить ссылки на функции стиля opengl?
  • 2
    взгляните на glprogramming.com/red/chapter01.html#name3
Показать ещё 7 комментариев
199

Да

За время, прошедшее с момента запроса этого вопроса, стандартный 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, если вы попытаетесь передать ему буквенный символ, например и вам нужно немного потрудиться, если вы хотите, чтобы ваши перегрузки поддерживали строковые литералы, Тем не менее в целом довольно прохладно, хотя.

  • 0
    Исходя из вашего примера, похоже, что единственная перегружаемая функция - это макросы. Позвольте мне понять, правильно ли я понимаю: если вы хотите перегружать функции, вы просто используете препроцессор для перенаправления вызова функции на основе переданных типов данных, верно?
  • 0
    Увы, всякий раз, когда C11 начинает завоевывать популярность, я предполагаю, что MISRA не будет использовать эту функцию по тем же причинам, по которым они запрещают списки переменных аргументов. Я стараюсь придерживаться MISRA довольно близко в моем мире.
Показать ещё 5 комментариев
71

Как уже говорилось, перегрузка в том смысле, что вы имеете в виду, не поддерживается 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); 
}
  • 21
    Почему бы вам просто не превратить все что whatever в отдельные функции ( set_int , set_float и т. Д.). Затем «тегирование с типом» становится «добавить имя типа к имени функции». Версия в этом ответе включает в себя больше печатания, больше затрат времени выполнения, больше шансов на ошибки, которые не будут обнаружены во время компиляции ... Я не вижу никакого преимущества в таких действиях! 16 голосов ?!
  • 16
    Бен, за этот ответ проголосовали, потому что он отвечает на вопрос, а не просто говорит «не делай этого». Вы правы, что в C более идиоматично использовать отдельные функции, но если кто-то хочет полиморфизма в C, это хороший способ сделать это. Кроме того, этот ответ показывает, как вы реализуете полиморфизм во время выполнения в компиляторе или виртуальной машине: помечайте значение типом, а затем отправляйте на основе этого. Таким образом, это отличный ответ на оригинальный вопрос.
Показать ещё 1 комментарий
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)
16

Вот самый яркий и самый краткий пример, который я нашел, демонстрируя перегрузку функций в 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 по духу (но с меньшим количеством объяснения).
  • 0
    Я определенно предпочитаю 1 единственный непрерывный блок полного и работоспособного кода отбивному с нарезкой и нарезкой кубиками, которое является # 1240268. Каждому свое.
Показать ещё 1 комментарий
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; печать (а); печать ( "привет"); возврат (EXIT_SUCCESS); } выведет 0 и привет .. из printA и printB ...
  • 1
    __builtin_types_compatible_p, этот компилятор GCC не специфичен?
11

Это может вообще не помогать, но если вы используете clang, вы можете использовать перегружаемый атрибут. Это работает даже при компиляции как 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) { ... }
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 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 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;
}
9

В смысле вы имеете в виду - нет, вы не можете.

Вы можете объявить функцию va_arg, например

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

но вам нужно будет передать некоторую информацию о количестве переменных и их типах в первом аргументе - например, printf().

6

Обычно бородавка, указывающая тип, добавляется или добавляется к имени. Вы можете уйти с макросами, это некоторые примеры, но это скорее зависит от того, что вы пытаетесь сделать. Там нет полиморфизма в C, только принуждение.

Простые общие операции могут выполняться с помощью макросов:

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

Если ваш компилятор поддерживает typeof, более сложные операции можно поместить в макрос. Затем вы можете иметь символ foo (x) для поддержки одной и той же операции разных типов, но вы не можете изменять поведение разных перегрузок. Если вам нужны фактические функции, а не макросы, вы можете вставить тип в имя и использовать второе вложение для доступа к нему (я не пробовал).

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

Ответ Leushenko действительно классный - только: пример 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... Нам просто нужно немного модифицировать макросы для наших нужд (я буду использовать более короткие имена и меньше аргументов для краткости).

#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, я бы рекомендовал вместо этого variadic functions.

  • 3
    Нет, если компилятор C ++ недоступен для ОС, для которой он кодирует.
  • 0
    Да, если я спрошу, что ос
Показать ещё 1 комментарий
-2

Надеюсь, что приведенный ниже код поможет вам понять перегрузку функций

#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
    Ответ должен объяснить, что он делает и почему он работает. Если это не так, как это может помочь кому-нибудь понять что-нибудь?
  • 0
    Здесь нет перегрузок.
-4

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню