Function overloading in C using GCC - functions with mutiple arguments

耗尽温柔 提交于 2020-01-05 11:14:10

问题


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.

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.


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)

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)

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

example usage:

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

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

Also remember, since this is C99 the _Generic keyword is not available.


回答1:


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:

  • ... the solution isn't portable, because the core gimmick of choosing arguments via types is already GCC specific.
  • ... the solution is build on macros. Finding syntax errors in macros is difficult, because the error messages refer to expanded code that the user doesn't see.
  • ... the solution pollutes the namespace with many macro names. If you really want to use this solution, prefix all your macros (except the most visible ones) consistenty as to minimize the danger of symbol collision.

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

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:

Variadic constant-type macros

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.

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.

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;
}

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

Variant type

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;
};

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

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)

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; }

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?)

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

Applying the macro to all arguments of a function

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__)

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.

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.

The implementation

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');
}

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.




回答2:


This solution is in no way generic, but it will get the job done for the very specific case asked in the question.

#include <stdio.h>

#define print(...) \
        __builtin_choose_expr(__builtin_types_compatible_p(typeof(FIRST(__VA_ARGS__)), int), print_int, print_string)\
(__VA_ARGS__)

#define FIRST(A, ...) A

void print_int(int i, int j) {
    printf("int: %d %d\n", i, j);
}

void print_string(char* s) {
    printf("char*: %s\n", s);
}

int main(int argc, char* argv[]) {

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

    return 0;
}

If anyone can find a more generalized solution that will work consistently when new overloads are added that would be greatly appreciated.



来源:https://stackoverflow.com/questions/36551163/function-overloading-in-c-using-gcc-functions-with-mutiple-arguments

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!