不使用 _Generic 的 C 函数重载
Function overloading in C without using _Generic
我希望在 C 中实现函数重载,但我试图 运行 我的代码在不支持 C11 的 Unix 服务器上,因此 _Generic
关键字不可用。
(升级服务器使其具有更新版本的 GCC 不是一种选择)。
除了使用 _Generic
在 C 语言中模拟有效的函数重载之外,还有其他替代方法吗?
您可以对某些参数类型进行有限形式的重载,例如:
void func_int(int);
void func_long(long);
void func_longlong(long long);
#define FUNC(X) \
(sizeof(X) <= sizeof(int) ? func_int(X) \
: sizeof(X) == sizeof(long) ? func_long(X) \
: func_longlong(X))
这将允许您使用 FUNC(i)
并让它调用不同的函数。它是有限的,因为您只能通过大小来区分类型。这意味着如果 sizeof(int) == sizeof(long)
那么你永远不会调用 func_long
,如果 sizeof(long) == sizeof(long long)
那么你永远不会调用 func_longlong
。此外,如果 sizeof(double)
与您正在测试的整数类型之一相同,则不能重载其他类型,例如 double
。
它可用于超载,例如float
、double
或 long double
,其中您可能有不同的函数实现,这些函数的计算或多或少取决于参数类型中的精度(即位数)。
我找到了一个似乎有效的方法,但是在编译时我仍然收到一些警告...
工作代码:
#include <stdio.h>
#define print(x) \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int ), print_int(x) , \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), char[]), print_string(x), \
(void)0))
void print_int(int i) {
printf("int: %d\n", i);
}
void print_string(char* s) {
printf("char*: %s\n", s);
}
int main(int argc, char* argv[]) {
print(1);
print("this");
return 0;
}
输出:
int: 1
char*: thing
编译器警告:
gcc overload.c -o main
overload.c: In function 'main':
overload.c:19: warning: passing argument 1 of 'print_string' makes pointer from integer without a cast
overload.c:20: warning: passing argument 1 of 'print_int' makes integer from pointer without a cast
GCC manual explicitly shows a GNU99 (-std=gnu99
) workaround since at least version 3.1.1.
当然有限制:所有变体必须具有相同的 return 类型,并且所有函数变体都必须具有句法意义。后者通常是各种编译错误的原因(函数变体参数的类型无效)。这可以通过声明没有参数原型的函数来避免;但是,必须记住默认类型提升将发生(float
被提升为 double
,所有小于 int
的整数类型被提升为 int
或 unsigned int
).考虑这个示例程序:
#define _GNU_SOURCE /* for asprintf() */
#include <stdlib.h>
#include <stdio.h>
typedef struct {
double x;
double y;
double z;
double d;
} plane;
static const char *foo_char_array();
static const char *foo_int();
static const char *foo_long();
static const char *foo_double();
static const char *foo_float();
static const char *foo_short();
static const char *foo_plane();
#define foo(x) \
( __builtin_choose_expr( __builtin_types_compatible_p(typeof(x), int), foo_int(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), long), foo_long(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), short), foo_short(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), float), foo_float(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), double), foo_double(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), plane), foo_plane(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), char []), foo_char_array(x), \
(void)0 ))))))) )
int main(void)
{
double d = 1.0;
float f = 2.0f;
short s = 3;
long n = 4L;
plane p = { 5.0, 6.0, 7.0, 8.0 };
printf("foo(9) = %s\n", foo(9));
printf("foo(10L) = %s\n", foo(10L));
printf("foo(11.0f) = %s\n", foo(11.0f));
printf("foo(12.0) = %s\n", foo(12.0));
printf("foo(\"bar\") = %s\n", foo("bar"));
printf("foo(d) = %s\n", foo(d));
printf("foo(f) = %s\n", foo(f));
printf("foo(s) = %s\n", foo(s));
printf("foo(n) = %s\n", foo(n));
printf("foo(p) = %s\n", foo(p));
return EXIT_SUCCESS;
}
static const char *foo_char_array(char x[]) { return "char []"; }
static const char *foo_int(int x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "(int)%d", x); return (const char *)buffer; }
static const char *foo_long(long x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "(long)%ld", x); return (const char *)buffer; }
static const char *foo_float(double x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "%af", x); return (const char *)buffer; }
static const char *foo_double(double x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "%a", x); return (const char *)buffer; }
static const char *foo_short(int x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "(short)%d", x); return (const char *)buffer; }
static const char *foo_plane(plane p) { static char buffer[120]; snprintf(buffer, sizeof buffer, "(plane){ .x=%g, .y=%g, .z=%g, .d=%g }", p.x, p.y, p.z, p.d); return (const char *)buffer; }
不需要根据单个参数来判断类型;你可以做例如__builtin_types_compatible_p(typeof(x), double) && __builtin_types_compatible_p(typeof(y), double)
验证 x
和 y
都是 double
.
类型
编译后运行,以上程序会输出
foo(9) = (int)9
foo(10L) = (long)10
foo(11.0f) = 0x1.6p+3f
foo(12.0) = 0x1.8p+3
foo("bar") = char []
foo(d) = 0x1p+0
foo(f) = 0x1p+1f
foo(s) = (short)3
foo(n) = (long)4
foo(p) = (plane){ .x=5, .y=6, .z=7, .d=8 }
在 32 位 x86 Linux (ILP32) 以及 x86-64 (LP64) 上测试。是的,上面的程序会泄漏内存,因为它从不 free()
s 动态分配的字符串 return 由 foo_..()
函数变体编辑。
在联合中使用函数指针和无名结构是有可能的。下面是一个我们重载 add 和 mul 函数的例子。有两个联合 LIBI 和包含无名结构的 LIBF。 LIBI 包含仅使用整数值的函数指针 add 和 mulc。 LIBF 与 LIBI 相同,只是 add 和 mul 使用 float 变量。此外,我们需要在这些联合之外创建 addi、muli、addf 和 mulf 函数。联合中的函数指针将引用这 4 个函数。例如,LIBI 中的 add 称为 addi,因为 addi 使用 int 值,而 LIBF 中的 add 称为 addf,因为它仅使用 float 变量。此示例还可以用作模拟 C 语言中不存在的命名空间的方法。在此示例中,联合的作用类似于名称空间。
#include<stdio.h>
#include<stdlib.h>
union {
struct {
void (*add)(int *, int);
void (*mul)(int *, int);
};
}LIBI;
union {
struct {
void (*add)(float *, float);
void (*mul)(float *, float);
};
}LIBF;
void addi(int *a, int c){
*a += c;
}
void addf(float *a, float c){
*a += c;
}
void muli(int *a, int c){
*a *= c;
}
void mulf(float *a, float c){
*a *= c;
}
int main(void){
LIBI.add = addi;
LIBF.add = addf;
LIBI.mul = muli;
LIBF.mul = mulf;
int ia = 10;
int ib = 2;
float fa = 20.0f;
float fb = 2.0f;
LIBI.add(&ia,ib);
LIBF.add(&fa,fb);
printf("%d\n",ia);
printf("%f\n",fa);
LIBI.mul(&ia,ib);
LIBF.mul(&fa,fb);
printf("%d\n",ia);
printf("%f\n",fa);
return 0;
}
我希望在 C 中实现函数重载,但我试图 运行 我的代码在不支持 C11 的 Unix 服务器上,因此 _Generic
关键字不可用。
(升级服务器使其具有更新版本的 GCC 不是一种选择)。
除了使用 _Generic
在 C 语言中模拟有效的函数重载之外,还有其他替代方法吗?
您可以对某些参数类型进行有限形式的重载,例如:
void func_int(int);
void func_long(long);
void func_longlong(long long);
#define FUNC(X) \
(sizeof(X) <= sizeof(int) ? func_int(X) \
: sizeof(X) == sizeof(long) ? func_long(X) \
: func_longlong(X))
这将允许您使用 FUNC(i)
并让它调用不同的函数。它是有限的,因为您只能通过大小来区分类型。这意味着如果 sizeof(int) == sizeof(long)
那么你永远不会调用 func_long
,如果 sizeof(long) == sizeof(long long)
那么你永远不会调用 func_longlong
。此外,如果 sizeof(double)
与您正在测试的整数类型之一相同,则不能重载其他类型,例如 double
。
它可用于超载,例如float
、double
或 long double
,其中您可能有不同的函数实现,这些函数的计算或多或少取决于参数类型中的精度(即位数)。
我找到了一个似乎有效的方法,但是在编译时我仍然收到一些警告...
工作代码:
#include <stdio.h>
#define print(x) \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int ), print_int(x) , \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), char[]), print_string(x), \
(void)0))
void print_int(int i) {
printf("int: %d\n", i);
}
void print_string(char* s) {
printf("char*: %s\n", s);
}
int main(int argc, char* argv[]) {
print(1);
print("this");
return 0;
}
输出:
int: 1
char*: thing
编译器警告:
gcc overload.c -o main
overload.c: In function 'main':
overload.c:19: warning: passing argument 1 of 'print_string' makes pointer from integer without a cast
overload.c:20: warning: passing argument 1 of 'print_int' makes integer from pointer without a cast
GCC manual explicitly shows a GNU99 (-std=gnu99
) workaround since at least version 3.1.1.
当然有限制:所有变体必须具有相同的 return 类型,并且所有函数变体都必须具有句法意义。后者通常是各种编译错误的原因(函数变体参数的类型无效)。这可以通过声明没有参数原型的函数来避免;但是,必须记住默认类型提升将发生(float
被提升为 double
,所有小于 int
的整数类型被提升为 int
或 unsigned int
).考虑这个示例程序:
#define _GNU_SOURCE /* for asprintf() */
#include <stdlib.h>
#include <stdio.h>
typedef struct {
double x;
double y;
double z;
double d;
} plane;
static const char *foo_char_array();
static const char *foo_int();
static const char *foo_long();
static const char *foo_double();
static const char *foo_float();
static const char *foo_short();
static const char *foo_plane();
#define foo(x) \
( __builtin_choose_expr( __builtin_types_compatible_p(typeof(x), int), foo_int(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), long), foo_long(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), short), foo_short(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), float), foo_float(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), double), foo_double(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), plane), foo_plane(x), \
__builtin_choose_expr( __builtin_types_compatible_p(typeof(x), char []), foo_char_array(x), \
(void)0 ))))))) )
int main(void)
{
double d = 1.0;
float f = 2.0f;
short s = 3;
long n = 4L;
plane p = { 5.0, 6.0, 7.0, 8.0 };
printf("foo(9) = %s\n", foo(9));
printf("foo(10L) = %s\n", foo(10L));
printf("foo(11.0f) = %s\n", foo(11.0f));
printf("foo(12.0) = %s\n", foo(12.0));
printf("foo(\"bar\") = %s\n", foo("bar"));
printf("foo(d) = %s\n", foo(d));
printf("foo(f) = %s\n", foo(f));
printf("foo(s) = %s\n", foo(s));
printf("foo(n) = %s\n", foo(n));
printf("foo(p) = %s\n", foo(p));
return EXIT_SUCCESS;
}
static const char *foo_char_array(char x[]) { return "char []"; }
static const char *foo_int(int x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "(int)%d", x); return (const char *)buffer; }
static const char *foo_long(long x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "(long)%ld", x); return (const char *)buffer; }
static const char *foo_float(double x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "%af", x); return (const char *)buffer; }
static const char *foo_double(double x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "%a", x); return (const char *)buffer; }
static const char *foo_short(int x) { static char buffer[40]; snprintf(buffer, sizeof buffer, "(short)%d", x); return (const char *)buffer; }
static const char *foo_plane(plane p) { static char buffer[120]; snprintf(buffer, sizeof buffer, "(plane){ .x=%g, .y=%g, .z=%g, .d=%g }", p.x, p.y, p.z, p.d); return (const char *)buffer; }
不需要根据单个参数来判断类型;你可以做例如__builtin_types_compatible_p(typeof(x), double) && __builtin_types_compatible_p(typeof(y), double)
验证 x
和 y
都是 double
.
编译后运行,以上程序会输出
foo(9) = (int)9
foo(10L) = (long)10
foo(11.0f) = 0x1.6p+3f
foo(12.0) = 0x1.8p+3
foo("bar") = char []
foo(d) = 0x1p+0
foo(f) = 0x1p+1f
foo(s) = (short)3
foo(n) = (long)4
foo(p) = (plane){ .x=5, .y=6, .z=7, .d=8 }
在 32 位 x86 Linux (ILP32) 以及 x86-64 (LP64) 上测试。是的,上面的程序会泄漏内存,因为它从不 free()
s 动态分配的字符串 return 由 foo_..()
函数变体编辑。
在联合中使用函数指针和无名结构是有可能的。下面是一个我们重载 add 和 mul 函数的例子。有两个联合 LIBI 和包含无名结构的 LIBF。 LIBI 包含仅使用整数值的函数指针 add 和 mulc。 LIBF 与 LIBI 相同,只是 add 和 mul 使用 float 变量。此外,我们需要在这些联合之外创建 addi、muli、addf 和 mulf 函数。联合中的函数指针将引用这 4 个函数。例如,LIBI 中的 add 称为 addi,因为 addi 使用 int 值,而 LIBF 中的 add 称为 addf,因为它仅使用 float 变量。此示例还可以用作模拟 C 语言中不存在的命名空间的方法。在此示例中,联合的作用类似于名称空间。
#include<stdio.h>
#include<stdlib.h>
union {
struct {
void (*add)(int *, int);
void (*mul)(int *, int);
};
}LIBI;
union {
struct {
void (*add)(float *, float);
void (*mul)(float *, float);
};
}LIBF;
void addi(int *a, int c){
*a += c;
}
void addf(float *a, float c){
*a += c;
}
void muli(int *a, int c){
*a *= c;
}
void mulf(float *a, float c){
*a *= c;
}
int main(void){
LIBI.add = addi;
LIBF.add = addf;
LIBI.mul = muli;
LIBF.mul = mulf;
int ia = 10;
int ib = 2;
float fa = 20.0f;
float fb = 2.0f;
LIBI.add(&ia,ib);
LIBF.add(&fa,fb);
printf("%d\n",ia);
printf("%f\n",fa);
LIBI.mul(&ia,ib);
LIBF.mul(&fa,fb);
printf("%d\n",ia);
printf("%f\n",fa);
return 0;
}