Как добиться перегрузки функций в C?
есть ли способ достичь перегрузки функций в C? Я смотрю на простые функции, которые будут перегружены, как
foo (int a)
foo (char b)
foo (float c , int d)
Я думаю, что нет прямого пути; я ищу обходные пути, если таковые существуют.
14 ответов:
есть несколько вариантов:
- функции стиля printf (введите в качестве аргумента)
- функции стиля opengl (введите имя функции)
- C подмножество c++ (если вы можете использовать компилятор c++)
да!
в то время, как этот вопрос был задан, стандартный 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
- the тип управляющего выражения -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
если вы попытаетесь передать ему символьный литерал, например,и вам нужно немного пошалить если вы хотите, чтобы ваш перегрузки для поддержки строковых литералов. Тем не менее, в целом довольно круто.
как уже говорилось, перегрузка в том смысле, что вы имеете в виду, не поддерживается C. общая идиома для решения проблемы заключается в том, что функция принимает tagged union. Это реализуется с помощью
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); }
Если ваш компилятор gcc, и вы не возражаете делать ручные обновления каждый раз, когда вы добавляете новую перегрузку, вы можете сделать некоторую магию макросов и получить результат, который вы хотите с точки зрения вызывающих абонентов, это не так приятно писать... но это возможно
посмотрите на _ _ builtin _ types_compatible_p, а затем используйте его для определения макроса, который делает что-то вроде
#define foo(a) \ ((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)
Но да противно, просто не
EDIT: C1X будет получать поддержку для универсальных выражений типа они выглядит так:
#define cbrt(X) _Generic((X), long double: cbrtl, \ default: cbrt, \ float: cbrtf)(X)
вот самый ясный и самый краткий пример, который я нашел, демонстрируя перегрузку функций в 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; }
Да, вроде того.
вот вам пример:
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.
следующий подход похож на a2800276 ' s, но с некоторой магией макроса 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; }
Это может не помочь вообще, но если вы используете лязг можно использовать атрибут перегружаемые - это работает, даже при компиляции как 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) { ... }
в том смысле, что вы имеете в виду-нет, вы не можете.
вы можете объявить Как
void my_func(char* format, ...);
, но вам нужно будет передать какую - то информацию о количестве переменных и их типах в первом аргументе-например
printf()
делает.
обычно бородавка для обозначения типа добавляется или добавляется к имени. Вы можете уйти с макросами в некоторых случаях, но это скорее зависит от того, что вы пытаетесь сделать. В C нет полиморфизма, только принуждение.
простые универсальные операции можно выполнять с помощью макросов:
#define max(x,y) ((x)>(y)?(x):(y))
Если ваш компилятор поддерживает typeof, более сложные операции могут быть помещены в макрос. Затем вы можете иметь символ foo (x) для поддержки той же операции различные типы, но вы не можете изменить поведение между различными перегрузками. Если вам нужны фактические функции, а не макросы, вы можете вставить тип в имя и использовать вторую вставку для доступа к нему (я не пробовал).
ответ Леушенко действительно круто-исключительно: the
foo
пример не компилируется с GCC, который неfoo(7)
, спотыкаясь оFIRST
макрос и фактический вызов функции ((_1, __VA_ARGS__)
, оставаясь с лишней запятой. Кроме того, мы находимся в беде, если мы хотим обеспечить дополнительные перегрузки, такие какfoo(double)
.поэтому я решил уточнить ответ немного дальше, в том числе разрешить перегрузку void (
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 \ ) \ )
хорошо, я уже добавил пустую перегрузку – однако это на самом деле не покрывается стандартом C, который не допускает пустых вариативных аргументов, т. е. мы тогда полагаться на расширения компилятора!
сначала пустой вызов макроса (
foo()
) по-прежнему производит маркер, но пустой. Таким образом, макрос подсчета фактически возвращает 1 вместо 0 даже при пустом вызове макроса. Мы можем "легко" устранить эту проблему, если поставим запятую после__VA_ARGS__
условно в зависимости от списка пусто или нет:#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
это посмотрел легко, но
COMMA
макрос довольно тяжелый; к счастью, тема уже освещена в блог Йенс Gustedt (спасибо, Йенс). Основной трюк заключается в том, что макросы функций не расширяются, если за ними не следуют скобки, для дальнейших объяснений взгляните на блог Йенса... Нам просто нужно немного изменить макросы в соответствии с нашими потребностями (я буду использовать более короткие имена и меньше аргументов для краткость.)#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; }
вы не можете просто использовать C++ и не использовать все другие функции C++, кроме этого?
Если все еще нет только строгого C, то я бы рекомендовал variadic функции.
попробуйте объявить эти функции как
extern "C++"
Если ваш компилятор поддерживает это,http://msdn.microsoft.com/en-us/library/s6y4zxec (VS. 80). aspx
Я надеюсь, что приведенный ниже код поможет вам понять функцию перегрузки
#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 *)); }