Чи є спосіб досягти перевантаження функцій у С? Я дивлюся на прості функції, як, наприклад, перевантажуватися
foo (int a)
foo (char b)
foo (float c , int d)
Я думаю, що прямого прямого шляху немає; Я шукаю обхідні шляхи, якщо такі існують.
Чи є спосіб досягти перевантаження функцій у С? Я дивлюся на прості функції, як, наприклад, перевантажуватися
foo (int a)
foo (char b)
foo (float c , int d)
Я думаю, що прямого прямого шляху немає; Я шукаю обхідні шляхи, якщо такі існують.
Відповіді:
Є кілька можливостей:
Так!
За час, коли це питання було задано, стандартний 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 і вибрати вираження результату на основі типу аргументів, переданих керуючому макросу. Отже (приклад зі стандарту С):
#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. Загальною ідіомою для вирішення проблеми є змушення функції приймати теговий союз . Це реалізується 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);
}
whatever
S на окремі функції ( set_int
, set_float
і т.д.). Потім "позначення типу" стає "додавання імені типу до імені функції". Версія у цій відповіді передбачає більше набору тексту, більше витрат на виконання, більше шансів на помилки, які не будуть виявлені під час компіляції ... Я не бачу жодної переваги робити такі дії! 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;
}
Якщо ваш компілятор - 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)
Так, начебто.
Ось вам на приклад:
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 , але з доданими деякими макро магіями 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) { ... }
Зазвичай бородавка для позначення типу додається або додається до імені. Ви можете піти з макросів - це деякі випадки, але це швидше залежить від того, що ви намагаєтеся зробити. У С немає поліморфізму, лише примус.
Прості загальні операції можна виконати за допомогою макросів:
#define max(x,y) ((x)>(y)?(x):(y))
Якщо ваш компілятор підтримує typeof , в макрос можна помістити більш складні операції. Потім ви можете мати символ foo (x) для підтримки однієї і тієї ж операції різних типів, але ви не можете змінювати поведінку між різними перевантаженнями. Якщо вам потрібні фактичні функції, а не макроси, ви, можливо, зможете вставити тип до імені та скористатися другою вставкою для доступу до нього (я не намагався).
Відповідь Левшенка справді класна - виключно: 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 \
) \
)
Гаразд, я вже додав порожнечу перевантаження - однак ця фактично не охоплена стандартом C, що не дозволяє порожні різні аргументи, тобто ми покладаємось на розширення компілятора !
Спочатку порожній макро дзвінок ( foo()
) все ще виробляє маркер, але порожній. Отже макрос підрахунку фактично повертає 1 замість 0 навіть при порожньому виклику макросу. Ми можемо «легко» усунути цю проблему, якщо поставити кому після __VA_ARGS__
умовно , залежно від того, чи список порожній чи ні:
#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
Це виглядало легко, але COMMA
макрос досить важкий; на щастя, тема вже висвітлена в блозі Йенса Гуведта (спасибі, Єнс). Основна хитрість полягає в тому, що макроси функцій не розширюються, якщо не супроводжуються дужками, для подальшого пояснення ознайомтеся з блогом Йенса ... Нам просто потрібно трохи змінити макроси під наші потреби (я буду використовувати короткі назви і менше аргументів для стислості).
#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, то я б рекомендував натомість різні варіанти .
Спробуйте оголосити ці функції так, 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 *));
}