使用GCC在C中进行函数重载-具有多个参数的函数 [英] Function overloading in C using GCC - functions with mutiple arguments

查看:100
本文介绍了使用GCC在C中进行函数重载-具有多个参数的函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在上一个问题中,我找到了一种在C99中重载函数的方法,当每个函数仅接受一个参数时。有关详细信息,请参见:使用GCC在C中进行函数重载-编译器警告

In a previous question I found a way to overload functions in C99 when each function only took a single argument. See the answers in: Function overloading in C using GCC - compiler warnings for details.

现在,我已经找到了一种使用单参数函数的方法,我想知道对于带有多个参数的函数如何做到这一点。我认为这与 __ VA_ARGS __ 和使用 ... 有关,但是我似乎找不到任何东西

Now that I've found a way to do it with single argument functions I'm wondering how this can be done for functions that take multiple arguments. I assume it will have something to do with __VA_ARGS__ and using ... but I can't seem to find anything that works or even wants to compile.

这将适用于带有2个参数的打印:

This will work for a print that takes 2 arguments:

#define print(x, y)                                                            \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int) &&          \
                      __builtin_types_compatible_p(typeof(y), int), print_int, \
(void)0)(x, y)

但是如果我还想要另一个带有一个参数的版本,则无法重新定义。添加此内容会给我一个错误,提示您重新定义 print

But if I also want another version that takes one argument I can't redefine it. Adding this will give me an error saying print is redefined:

#define print(x)                                                                     \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), char[]), print_string, \
(void)0)(x)






如何重载print,以便它将2个整数作为输入或一个字符使用


How can I overload print so it will work with 2 integers as an input or with a character array?

示例用法:

print(1, 2);
print("this");

甚至更好……我如何使其与任何类型的组合或任意数量的组合一起使用

Or even better... how can I make it work with any combination of types or any number of arguments?

还要记住,因为这是C99,所以_Generic关键字不可用。

推荐答案

您可以使用GCC的扩展程序和过量的预处理器技巧来完成所需的操作。评论者已经明确表达了自己的观点:C非常明确,并且与所产生的符号具有一对一的关系。如果需要函数重载和类型检查,请使用提供它们的多种语言之一。

You can do what you want with GCC's extensions and with an overdose of preprocessor tricks. The commenters have already made their opinion clear: C is rather explicit and has a one-to-one relationship with the symbols produced. If you want function overloading and type inspection, use one of the many languages that provide them.

巴洛克式宏解决方案通常是玩具,而不是适合生产的代码,但是突破极限仍然是一个有趣的练习。但是,安全性仍然存在,并要注意:

Baroque macro solutions tend to be toys rather than code that's suitable for production, but it's still an interesting exercise to push the envelope. Safety helemts on, though, and be aware that:


  • ...该解决方案不是可移植的,因为选择参数的核心mm头通过类型已经是特定于GCC的。

  • ...解决方案基于宏。在宏中查找语法错误非常困难,因为错误消息引用了用户看不到的扩展代码。

  • ...该解决方案污染了具有许多宏名称的名称空间。如果您确实要使用此解决方案,请为所有宏(最可见的宏除外)加前缀前缀,以最大程度地减少符号冲突的危险。

顺便说一句,让我们实现一个函数 put ,根据其类型将其参数写入 stdin

That out of the way, let's implement a function put that writes its arguments to stdin according to its type:

const char *name = "Fred";
double C = 12.5;

put(1, " ", 2);                         // 1 2
put("Hello, I'm ", name, "!");          // Hello, I'm Fred!
put(C, " Celsius");                     // 12.5 Celsius
put(C * 1.8 + 32.0, " Fahrenheit");     // 54.5 Fahrenheit

为简单起见,该解决方案最多只接受三个 int const char * double ,但最大数量参数是可扩展的。

For the sake of simplicity, the solution accepts only up to three arguments of either int, const char * or double, but the maximum number of arguments is extensible.

解决方案包括以下部分:

The solution consists of these parts:

假设您要有一个对所有参数求和的函数。参数的数量可能有所不同,但是所有参数的类型均为 double 。如果它们的类型不是 double ,则应将其升级为 double

Say you want to have a function that sums all arguments. The number of arguments may vary, but all arguments are of type double. If they are not of type double, they should be promoted to double.

可变参数函数不是一个很好的解决方案,因为它们会将每个单独类型的参数传递给函数。尝试将 sum(1、2、3)设置为 double 将产生灾难性的结果。

Variadic functions aren't a good solution, because they will pass the arguments to the function per individual type. trying to sum(1, 2, 3) as double will have disastrous results.

相反,您可以使用复合文字立即创建一个 double s数组。使用 sizeof 机制获取数组的长度。 (这些参数可能会有副作用,因为未评估 sizeof 内的数组,仅确定了其大小。)

Instead, you can use compound literals to create an array of doubles on the fly. Use the sizeof mechanism to get the length of the array. (The arguments may have side effects, because the array inside the sizeof isn't evaluated, only its size is determined.)

#define sum(...) sum_impl(sizeof((double[]){__VA_ARGS__})/ \
                 sizeof(double), (double[]){__VA_ARGS__})

double sum_impl(size_t n, double x[])
{
    double s = 0.0;

    while (n--) s += x[n];
    return s;
}

这将产生 6.0 double s上执行的计算中, sum(1、2、3)

This will yield 6.0 for sum(1, 2, 3) in a calculation performed on doubles.

您希望所有参数都具有相同的类型,但此类型应能够表示您的所有受支持的类型功能。创建变体的C方法是使用带标记的联合,即结构中的 union

You want all arguments to be of the same type, but this type should be able to represent all supported types of your function. The C way to create a variant is to use a tagged union, a union inside a struct:

typedef struct var_t var_t;

struct var_t {
    int type;
    union {
        int i;
        double f;
        const char *s;
    } data;
};

类型可以是枚举。我在这里根据 printf 格式使用字符常量。

The type could be an enumeration. I use charcter constants according the to printf formats here.

表达式的变体由宏 VAR ,本质上是您在上面发布的特定gcc:

The variant of an expression is determined with a macro VAR, which is essentially the gcc specific you have posted above:

#define CHOOSE __builtin_choose_expr
#define IFTYPE(X, T) __builtin_types_compatible_p(typeof(X), T)

#define VAR(X)                                          \
    CHOOSE(IFTYPE(X, int),          make_var_i,         \
    CHOOSE(IFTYPE(X, const char[]), make_var_s,         \
    CHOOSE(IFTYPE(X, const char *), make_var_s,         \
    CHOOSE(IFTYPE(X, double),       make_var_f,         \
                                    make_var_0))))(X)

该宏调用任何 make_var 函数。必须为每种有效类型定义以下函数:

The macro invokes any of the make_var functions. These functions must be defined for each valid type:

var_t make_var_i(int X)         { var_t v = {'i', {.i = X}}; return v; }
var_t make_var_s(const char *X) { var_t v = {'s', {.s = X}}; return v; }
var_t make_var_f(double X)      { var_t v = {'f', {.f = X}}; return v; }
var_t make_var_0()              { var_t v = {'#'}; return v; }

X 纳入类型-正如您已经发现的那样,依赖表达式不起作用。出于相同的原因,您也不能在此处将复合文字与指定的初始化程序一起使用。 (我说过用宏进行错误检查很困难,不是吗?)

Incorporating the X into the type-dependent expression doesn't work, as you have already found out. Neither can you use compound literals with designated initialisers here, probably for the same reasons. (I've said that error checking with macros is hard, haven't I?)

这是GCC唯一的特定部分;也可以通过C11的 _Generic 来实现。

This is the only GCC specific part; it could also be achieved with C11's _Generic.

必须将 VAR 宏应用于可变参数 put 的所有参数宏。在获得空列表之前,您无法处理可变参数的开头,因为您不能递归地扩展宏,但是您可以使用一种技巧来对宏的参数进行计数,然后扩展为具有这么多参数的宏:

You must apply the VAR macro to all arguments of your variadic put macro. You cannot process the head of the variadic arguments until you get an empty list, because you cannot expand macros recursively, but you can use a trick that counts the arguments to the macro and then expand to a macro that takes that many arguments:

#define PUT1(_1)            put_impl(1, (var_t[]){VAR(_1)})
#define PUT2(_1, _2)        put_impl(2, (var_t[]){VAR(_1), VAR(_2)})
#define PUT3(_1, _2, _3)    put_impl(3, (var_t[]){VAR(_1), VAR(_2), VAR(_3)})

#define SELECT_N(_1, _2, _3, N, ...) N

#define put(...) SELECT_N(__VA_ARGS__, PUT3, PUT2, PUT1)(__VA_ARGS__)

现在 put 需要1、2或3个参数。如果提供的数目超过3,则会收到一条模糊的错误消息,该错误消息与不提供太多参数无关。

Now put takes 1, 2 or 3 arguments. If you provide more than 3, you get an obscure error message that doesn't have anything to do with not providing too many arguments.

上面的代码将不接受空参数列表。使用GCC扩展名,## __ VA_ARGS (仅当变量列表不为空时才写逗号),您可以将其扩展为:

The code above will not accept an empty argument list. With the GCC entension , ##__VA_ARGS, which will write a comma only if the variadicargument list isn't empty, you can extend this to:

#define PUT0()              put_impl(0, NULL)
#define PUT1(_1)            put_impl(1, (var_t[]){VAR(_1)})
#define PUT2(_1, _2)        put_impl(2, (var_t[]){VAR(_1), VAR(_2)})
#define PUT3(_1, _2, _3)    put_impl(3, (var_t[]){VAR(_1), VAR(_2), VAR(_3)})

#define SELECT_N(X, _1, _2, _3, N, ...) N

#define put(...) SELECT_N(X, ##__VA_ARGS__, PUT3, PUT2, PUT1,PUT0)(__VA_ARGS__)

您可以根据需要将此解决方案扩展为任意多个参数。

You can extend this solution to arbitrarily many arguments if you like.

上面的宏调用函数 put_impl ,这是如何打印 n 个变体。经过以上所有技巧之后,这些函数非常简单:

The above macro invokes the function put_impl, which is the implementation of how to print an array of n variants. After all the tricks above, the functions is rather straightforward:

void put_impl(size_t n, const var_t var[])
{
    for (size_t i = 0; i < n; i++) {
        switch(var[i].type) {
        case 'i':   printf("%i", var[i].data.i); break;
        case 'f':   printf("%g", var[i].data.f); break;
        case 's':   printf("%s", var[i].data.s); break;
        case '#':   printf("[undef]"); break;
        }
    }

    putchar('\n');
}



将它们放在一起



以下程序使用上述方法打印一些相当愚蠢的东西。它不是可移植的,但是如果使用 gcc -std = gnu99 进行编译,则可以运行:

Putting it all together

The following program uses the method described above to print some rather silly stuff. It is not portable, but runs if compiled with gcc -std=gnu99:

#include <stdlib.h>
#include <stdio.h>

#define CHOOSE __builtin_choose_expr
#define IFTYPE(X, T) __builtin_types_compatible_p(typeof(X), T)

#define VAR(X)                                          \
    CHOOSE(IFTYPE(X, int),          make_var_i,         \
    CHOOSE(IFTYPE(X, const char[]), make_var_s,         \
    CHOOSE(IFTYPE(X, const char *), make_var_s,         \
    CHOOSE(IFTYPE(X, double),       make_var_f,         \
                                    make_var_0))))(X)

#define PUT0()              put_impl(0, NULL)
#define PUT1(_1)            put_impl(1, (var_t[]){VAR(_1)})
#define PUT2(_1, _2)        put_impl(2, (var_t[]){VAR(_1), VAR(_2)})
#define PUT3(_1, _2, _3)    put_impl(3, (var_t[]){VAR(_1), VAR(_2), VAR(_3)})

#define SELECT_N(X, _1, _2, _3, N, ...) N

#define put(...) SELECT_N(X, ##__VA_ARGS__, PUT3, PUT2, PUT1,PUT0)(__VA_ARGS__)

typedef struct var_t var_t;

struct var_t {
    int type;
    union {
        int i;
        double f;
        const char *s;
    } data;
};

var_t make_var_i(int X)         { var_t v = {'i', {.i = X}}; return v; }
var_t make_var_s(const char *X) { var_t v = {'s', {.s = X}}; return v; }
var_t make_var_f(double X)      { var_t v = {'f', {.f = X}}; return v; }
var_t make_var_0()              { var_t v = {'#'}; return v; }

void put_impl(size_t n, const var_t var[])
{
    for (size_t i = 0; i < n; i++) {
        switch(var[i].type) {
        case 'i':   printf("%i", var[i].data.i); break;
        case 'f':   printf("%g", var[i].data.f); break;
        case 's':   printf("%s", var[i].data.s); break;
        case '#':   printf("[undef]"); break;
        }
    }

    putchar('\n');
}

int main()
{
    const char *name = "Fred";
    double C = 12.5;

    put(1, " ", 2);
    put("Hello, I'm ", name, "!");
    put();
    put(C, " Celsius");
    put(C * 1.8 + 32.0, " Fahrenheit");

    return 0;
}

您可以为想要支持的参数的类型和数量而疯狂,但是请记住,您的宏丛林越大,维护和调试的难度就越大。

You can go crazy on the types and number of arguments you want to support, but keep inn mind that the bigger your jungle of macros gets, the harder it will be to maintain and to debug.

这篇关于使用GCC在C中进行函数重载-具有多个参数的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆