FL4*_*SOF 234 c overloading
有没有办法在C中实现函数重载?我正在寻找简单的函数来重载像
foo (int a)
foo (char b)
foo (float c , int d)
Run Code Online (Sandbox Code Playgroud)
我认为没有直接的方式; 我正在寻找解决方法,如果存在的话.
Leu*_*nko 218
是!
自问这个问题以来,由于在C11中添加了关键字,标准C(无扩展)已经有效地获得了对函数重载(而不是运算符)的支持_Generic.(自4.9版以来在GCC中受支持)
(重载不是真正"内置"的问题所示的方式,但实现类似的东西很容易.)
_Generic是sizeof和_Alignof.一样的系列中的编译时运算符.它在标准的6.5.1.1节中描述.它接受两个主要参数:表达式(在运行时不会被计算),以及看起来有点像switch块的类型/表达式关联列表._Generic获取表达式的整体类型,然后"切换"它以在列表中为其类型选择最终结果表达式:
_Generic(1, float: 2.0,
char *: "2",
int: 2,
default: get_two_object());
Run Code Online (Sandbox Code Playgroud)
上面的表达式求值为2- 控制表达式的类型int,因此它选择与int值关联的表达式.这一切都没有在运行时.(该default子句是可选的:如果将其关闭且类型不匹配,则会导致编译错误.)
这对于函数重载很有用,它可以由C预处理器插入,并根据传递给控制宏的参数类型选择结果表达式.所以(来自C标准的例子):
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
default: cbrt, \
float: cbrtf \
)(X)
Run Code Online (Sandbox Code Playgroud)
此宏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
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我们可以使用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"
}
Run Code Online (Sandbox Code Playgroud)
(这里实现)因此,通过一些努力,您可以减少样板量,使其看起来非常像具有本机支持重载的语言.
顺便说一句,已经可以在C99中重载参数(不是类型)的数量.
[1]请注意,C评估类型的方式可能会让你失望.foo_int例如,如果您尝试将字符文字传递给它,则会选择,如果您希望重载支持字符串文字,则需要稍微混淆一下.虽然整体还很酷.
Jac*_*icz 125
可能性很小:
a28*_*276 75
如前所述,C语言不支持你所指的意义上的重载.解决问题的一个常用习惯是使函数接受标记的联合.这是通过struct参数实现的,其中struct自身由某种类型指示符组成,例如enuma 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);
}
Run Code Online (Sandbox Code Playgroud)
Spu*_*d86 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):)
Run Code Online (Sandbox Code Playgroud)
但是讨厌,只是不要
编辑: C1X将获得类型泛型表达式的支持,它们看起来像这样:
#define cbrt(X) _Generic((X), long double: cbrtl, \
default: cbrt, \
float: cbrtf)(X)
Run Code Online (Sandbox Code Playgroud)
Jay*_*lor 18
这是我发现在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;
}
Run Code Online (Sandbox Code Playgroud)
https://gist.github.com/barosl/e0af4a92b2b8cabd05a7
Nau*_*cal 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);
}
Run Code Online (Sandbox Code Playgroud)
它将从printA和printB输出0和hello ..
Chr*_*oph 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;
}
Run Code Online (Sandbox Code Playgroud)
Ste*_*azy 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));
Run Code Online (Sandbox Code Playgroud)
履行
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }
Run Code Online (Sandbox Code Playgroud)
Qua*_*noi 10
从某种意义上说,你的意思是 - 不,你不能.
你可以声明一个va_arg像这样的函数
void my_func(char* format, ...);
,但你需要在第一个参数中传递关于变量数量及其类型的某种信息 - 就像printf()这样.
通常会在名称后附加或附加一个表示类型的疣.你可以在某些情况下使用宏,但它取决于你想要做什么.C中没有多态性,只有强制.
可以使用宏完成简单的通用操作:
#define max(x,y) ((x)>(y)?(x):(y))
Run Code Online (Sandbox Code Playgroud)
如果您的编译器支持typeof,则可以在宏中放置更复杂的操作.然后,您可以使用符号foo(x)来支持不同类型的相同操作,但不能改变不同重载之间的行为.如果你想要实际的功能而不是宏,你可以将类型粘贴到名称并使用第二个粘贴来访问它(我还没有尝试过).
Leushenko 的回答真的很酷 - 仅此而已:该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
Run Code Online (Sandbox Code Playgroud)
很好,我们决定使用SELECT_1or 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 \
) \
)
Run Code Online (Sandbox Code Playgroud)
好的,我已经添加了 void 重载 - 然而,C 标准实际上没有涵盖这个重载,它不允许空的可变参数,即我们依赖编译器扩展!
一开始,一个空的宏调用 ( foo()) 仍然产生一个标记,但是一个空的。因此,即使在空宏调用时,计数宏实际上也返回 1 而不是 0。我们可以“轻松”消除这个问题,如果我们将逗号放在__VA_ARGS__ conditionally之后,取决于列表是否为空:
#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
Run Code Online (Sandbox Code Playgroud)
这看起来很容易,但COMMA宏是相当沉重的;幸运的是,这个话题已经在Jens Gustedt的博客中提到了(感谢 Jens)。基本技巧是,如果后面没有括号,函数宏就不会展开,有关进一步的解释,请查看 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 ,
Run Code Online (Sandbox Code Playgroud)
现在我们很好...
一个块中的完整代码:
/*
* 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;
}
Run Code Online (Sandbox Code Playgroud)
我想,我刚刚找到了一个非常优雅的解决方案。
您可以(ab)使用函数指针来组合类型。
_Generic(((void (*)(typeof(1.4) , typeof(9.9) , typeof(4) ))0),
void (*)(int, double) : printf("int double\n"),
void (*)(double, double) : printf("double double\n"),
void (*)(double, double, int) : printf("double double int\n")
);
Run Code Online (Sandbox Code Playgroud)
有了一点宏观魔法......
#define PARENS ()
#define EXPAND(...) EXPAND4(EXPAND4(EXPAND4(EXPAND4(__VA_ARGS__))))
#define EXPAND4(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) __VA_ARGS__
#define FOR_EACH(macro, ...) \
__VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__)))
#define FOR_EACH_HELPER(macro, a1, ...) \
macro(a1) \
__VA_OPT__(, FOR_EACH_AGAIN PARENS (macro, __VA_ARGS__))
#define FOR_EACH_AGAIN() FOR_EACH_HELPER
#define mGROUP_TYPE(...) void (*)(__VA_ARGS__)
#define mGROUP_TYPE_INST(...) ((void (*)(FOR_EACH(typeof, __VA_ARGS__)))0)
Run Code Online (Sandbox Code Playgroud)
...你可以做非常美丽的事情:
#define myfoo(...) \
_Generic(mGROUP_TYPE_INST(__VA_ARGS__), \
mGROUP_TYPE(int, double) : printf("int double\n"), \
mGROUP_TYPE(double, double) : printf("double double\n"), \
mGROUP_TYPE(double, double, int) : printf("double double int\n"), \
mGROUP_TYPE(int, double, int) : printf("int double int\n") \
)
myfoo(1, 0.4);
myfoo(1, 0.4, 4);
Run Code Online (Sandbox Code Playgroud)
如果返回值始终相同,您甚至可以更进一步并使用实际的函数指针:
#define mFUNC_CASE(fn) typeof(&(fn)) : fn
#define mFUNC_GENERIC_INST(ret, ...) (ret (*) (FOR_EACH(typeof, __VA_ARGS__)))0
#define to_string(...) \
_Generic(mFUNC_GENERIC_INST(char *, __VA_ARGS__), \
mFUNC_CASE(int_to_string), \
mFUNC_CASE(float_to_string), \
mFUNC_CASE(file_to_string), \
mFUNC_CASE(vec3_to_string) \
) (__VA_ARGS__)
Run Code Online (Sandbox Code Playgroud)
FOR_EACH() 宏源自这里(有一些小的修改)。该宏还有其他变体,不需要__VA_OPT__. 如果您的编译器不支持这一点。