Czy jest jakiś sposób na osiągnięcie przeciążenia funkcji w C? Patrzę na proste funkcje do przeciążenia
foo (int a)
foo (char b)
foo (float c , int d)
Myślę, że nie ma prostej drogi; Szukam rozwiązań, jeśli takie istnieją.
Czy jest jakiś sposób na osiągnięcie przeciążenia funkcji w C? Patrzę na proste funkcje do przeciążenia
foo (int a)
foo (char b)
foo (float c , int d)
Myślę, że nie ma prostej drogi; Szukam rozwiązań, jeśli takie istnieją.
Odpowiedzi:
Istnieje kilka możliwości:
Tak!
W chwili, gdy pytanie zostało zadane, standardowy C (bez rozszerzeń) skutecznie zyskał obsługę przeciążania funkcji (nie operatorów), dzięki dodaniu _Generic
słowa kluczowego w C11. (obsługiwane w GCC od wersji 4.9)
(Przeładowanie nie jest tak naprawdę „wbudowane” w sposób pokazany w pytaniu, ale bardzo trudno jest wdrożyć coś, co działa w ten sposób.)
_Generic
jest operatorem czasu kompilacji w tej samej rodzinie co sizeof
i _Alignof
. Jest to opisane w standardowej sekcji 6.5.1.1. Akceptuje dwa główne parametry: wyrażenie (które nie będzie oceniane w czasie wykonywania) oraz listę skojarzeń typów / wyrażeń, która wygląda trochę jak switch
blok. _Generic
pobiera ogólny typ wyrażenia, a następnie „przełącza” je, aby wybrać wyrażenie wyniku końcowego z listy dla swojego typu:
_Generic(1, float: 2.0,
char *: "2",
int: 2,
default: get_two_object());
Powyższe wyrażenie ocenia na 2
- typ wyrażenia kontrolującego jest int
, więc wybiera wyrażenie powiązane z int
wartością. Nic z tego nie pozostaje w czasie wykonywania. ( default
Klauzula jest opcjonalna: jeśli ją odrzucisz, a typ nie będzie zgodny, spowoduje to błąd kompilacji).
Przydaje się to w przypadku przeciążenia funkcji, ponieważ może zostać wstawione przez preprocesor języka C i wybrać wyrażenie wynikowe na podstawie typu argumentów przekazanych do makra sterującego. Tak (przykład ze standardu C):
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
default: cbrt, \
float: cbrtf \
)(X)
To makro implementuje przeciążoną cbrt
operację, wysyłając typ makra do makra, wybierając odpowiednią funkcję implementacyjną, a następnie przekazując oryginalny argument makra do tej funkcji.
Aby wdrożyć oryginalny przykład, możemy to zrobić:
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
W tym przypadku moglibyśmy zastosować default:
skojarzenie dla trzeciego przypadku, ale to nie pokazuje, jak rozszerzyć zasadę na wiele argumentów. Efektem końcowym jest to, że możesz używać foo(...)
w swoim kodzie bez obawy (wiele [1]) o rodzaj jego argumentów.
W przypadku bardziej skomplikowanych sytuacji, np. Funkcji przeciążających większą liczbę argumentów lub zmieniających się liczb, można użyć makr narzędziowych do automatycznego generowania statycznych struktur wysyłki:
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"
}
( tutaj implementacja ) Przy odrobinie wysiłku możesz zredukować liczbę podstawek do wyglądu przypominającego język z natywną obsługą przeciążania.
Nawiasem mówiąc , w C99 było już możliwe przeciążenie liczbą argumentów (nie typu).
[1] zauważ, że sposób, w jaki C ocenia typy, może cię jednak potknąć. Spowoduje to wybranie, foo_int
jeśli na przykład spróbujesz przekazać literał znakowy, i musisz trochę zepsuć, jeśli chcesz, aby przeciążenia obsługiwały literały łańcuchowe. Mimo to ogólnie całkiem fajnie.
Jak już wspomniano, przeciążenie w tym sensie, że masz na myśli, że nie jest obsługiwane przez C. Częstym idiomem do rozwiązania problemu jest spowodowanie, aby funkcja zaakceptowała oznaczony związek . Jest to realizowane przez struct
parametr, w którym struct
sam składa się z pewnego rodzaju wskaźnika typu, takiego jak an enum
i union
różnego rodzaju wartości. Przykład:
#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 do oddzielnych funkcji ( set_int
, set_float
itp). Następnie „oznaczanie typem” staje się „dodaj nazwę typu do nazwy funkcji”. Wersja w tej odpowiedzi wymaga więcej pisania, większego kosztu działania, większej szansy na błędy, które nie zostaną wykryte podczas kompilacji ... Nie widzę żadnej korzyści z robienia tego w ten sposób! 16 głosów pozytywnych ?!
Oto najjaśniejszy i najbardziej zwięzły przykład, w którym wykazałem przeciążenie funkcji w 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;
}
Jeśli twoim kompilatorem jest gcc i nie masz nic przeciwko robieniu aktualizacji ręcznie za każdym razem, gdy dodajesz nowe przeciążenie, możesz wykonać magię makr i uzyskać pożądany efekt pod względem rozmówców, nie jest tak przyjemnie pisać ... ale jest to możliwe
spójrz na __builtin_types_compatible_p, a następnie użyj go do zdefiniowania makra, które działa podobnie
#define foo(a) \
((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)
ale tak, paskudne, po prostu nie
EDYCJA: C1X otrzyma wsparcie dla ogólnych wyrażeń typu, które wyglądają tak:
#define cbrt(X) _Generic((X), long double: cbrtl, \
default: cbrt, \
float: cbrtf)(X)
Tak, w pewnym sensie.
Oto przykład:
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);
}
Wyjdzie 0 i witam .. z printA i printB.
Poniższe podejście jest podobne do modelu a2800276 , ale z dodaną odrobiną magii makro 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;
}
To może wcale nie pomóc, ale jeśli używasz clang, możesz użyć atrybutu przeciążalnego - Działa to nawet podczas kompilacji jako C
http://clang.llvm.org/docs/AttributeReference.html#overloadable
nagłówek
extern void DecodeImageNow(CGImageRef image, CGContextRef usingContext) __attribute__((overloadable));
extern void DecodeImageNow(CGImageRef image) __attribute__((overloadable));
Realizacja
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }
Zwykle brodawka wskazująca typ jest dołączana lub dodawana do nazwy. Możesz uniknąć makr w niektórych przypadkach, ale raczej zależy to od tego, co próbujesz zrobić. W C nie ma polimorfizmu, tylko przymus.
Za pomocą makr można wykonywać proste operacje ogólne:
#define max(x,y) ((x)>(y)?(x):(y))
Jeśli twój kompilator obsługuje typeof , w makrze można umieścić bardziej skomplikowane operacje. Możesz wtedy mieć symbol foo (x), aby obsługiwać tę samą operację dla różnych typów, ale nie możesz zmieniać zachowania między różnymi przeciążeniami. Jeśli chcesz rzeczywistych funkcji zamiast makr, możesz być w stanie wkleić typ do nazwy i użyć drugiego wklejenia, aby uzyskać do niego dostęp (nie próbowałem).
Odpowiedź Leushenko jest naprawdę fajna - tylko: foo
przykład nie kompiluje się z GCC, który się nie udaje foo(7)
, potykając się o FIRST
makro i faktyczne wywołanie funkcji ( (_1, __VA_ARGS__)
pozostając z nadwyżką przecinka. Dodatkowo mamy kłopoty, jeśli chcemy zapewnić dodatkowe przeciążenia , takie jak foo(double)
.
Postanowiłem więc rozwinąć odpowiedź nieco dalej, w tym pozwolić na przeciążenie pustki ( foo(void)
- co spowodowało sporo problemów ...).
Pomysł jest teraz: Zdefiniuj więcej niż jeden rodzajowy w różnych makrach i pozwól wybrać poprawny zgodnie z liczbą argumentów!
Liczba argumentów jest dość łatwa, na podstawie tej odpowiedzi :
#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
To miłe, rozwiązujemy jeden SELECT_1
lub SELECT_2
(lub więcej argumentów, jeśli ich potrzebujesz / potrzebujesz), więc po prostu potrzebujemy odpowiednich definicji:
#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 \
) \
)
OK, dodałem już void overload - jednak ten faktycznie nie jest objęty standardem C, który nie dopuszcza pustych argumentów variadic, tzn. Polegamy na rozszerzeniach kompilatora !
Na początku puste wywołanie makra ( foo()
) nadal generuje token, ale puste. Makro liczące faktycznie zwraca 1 zamiast 0, nawet przy pustym wywołaniu makra. Możemy „łatwo” wyeliminować ten problem, jeśli __VA_ARGS__
warunkowo wstawimy przecinek , w zależności od pustej listy:
#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
To wydawało się łatwe, ale COMMA
makro jest dość ciężkie; na szczęście temat jest już omawiany na blogu Jensa Gustedta (dzięki, Jens). Podstawowa sztuczka polega na tym, że makra funkcyjne nie są rozwijane, jeśli nie są nawiasami, w celu uzyskania dalszych wyjaśnień zajrzyj na blog Jensa ... Musimy tylko trochę zmodyfikować makra do naszych potrzeb (zamierzam użyć krótszych nazw i mniej argumentów za zwięzłością).
#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 ,
A teraz mamy się dobrze ...
Pełny kod w jednym bloku:
/*
* 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;
}
Czy nie możesz po prostu używać C ++ i nie używać wszystkich innych funkcji C ++ oprócz tej?
Jeśli nadal nie jest to tylko ścisłe C, poleciłbym zamiast tego funkcje variadic .
Spróbuj zadeklarować te funkcje, extern "C++"
jakby Twój kompilator to obsługiwał, 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 *));
}