determine argument type from __VA_ARGS__ in compile time

吃可爱长大的小学妹 提交于 2020-01-04 08:15:38

问题


I wish to determine the types of the parameters passed to a function using VA_ARGS in order to route it to the right handler, but in compile time (and not inside a function with va_args()).

by determine type i mean i need to know if the trace contains only integers or has strings in it as well, but i wish it will be in compile time.

for example:

#define TRACE_HANDLER(type_branch) (Invoke_ ## type_branch)  

#define TYPE_ARGS(args) ______//Determine if all arguments are uint32________

#define TRACE_(formatString,...)  TRACE_HANDLER(TYPE_ARGS(__VA_ARGS__))(__VA_ARGS__)  

#define TRACE(Id,formatString,...) TRACE_(formatString,__VA_ARGS__)

any ideas?

thanks!


回答1:


You can do a compile-time dispatch on the type of an expression with the _Generic operator. Note that this is part of the main C language, not preprocessor macros.

int x = 0;
_Generic(x, int: invoke_int,
            float: invoke_float,
            double: invoke_double)(x);  //calls invoke_int with x

The expression you give as the first argument to _Generic is only used for its type, at compile-time, to select a value to inline (in this case, a function to pass the runtime variable).


_Generic is only intended for use with a single parameter, and as a consequence most examples only show how to overload functions with a single argument. You could engage in some hefty metaprogramming to create deeply-nested _Generic trees that chew their way through all passed arguments, but here's one much simpler possible way to overload a function with multiple arguments of multiple types:

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

// specialized definitions
void overload_1(int a, int b, int c) {
    printf("all ints (%d, %d, %d)\n", a, b, c);
}

void overload_2(int a, char * b, int c) {
    printf("b is a string (%d, %s, %d)\n", a, b, c);
}

void overload_3(char * a, int b, char * c) {
    printf("a and c are strings (%s, %d, %s)\n", a, b, c);
}

void static_error(int l) { printf("error with overload on %d\n", l); exit(1); } 

// type indices
enum ARG_TYPE {
    INT = 0, CHAR_P
};

// get the ID of a specialization by the list of arg types
static inline int get_overload_id(int ac, int av[]) {
    return (ac == 3 && av[0] == INT && av[1] == INT && av[2] == INT)       ? 1
         : (ac == 3 && av[0] == INT && av[1] == CHAR_P && av[2] == INT)    ? 2
         : (ac == 3 && av[0] == CHAR_P && av[1] == INT && av[2] == CHAR_P) ? 3
         : -1   //error
    ;
}

// overloaded definition
#define overload(...) overload_ex(get_overload_id(M_NARGS(__VA_ARGS__), (int[]){ M_FOR_EACH(GET_ARG_TYPE, __VA_ARGS__) }), __VA_ARGS__)
#define overload_ex(getID, ...) \
    ((getID == 1) ? overload_1(GET_ARG(0, INT, __VA_ARGS__), GET_ARG(1, INT, __VA_ARGS__), GET_ARG(2, INT, __VA_ARGS__)) \
    :(getID == 2) ? overload_2(GET_ARG(0, INT, __VA_ARGS__), GET_ARG(1, CHAR_P, __VA_ARGS__), GET_ARG(2, INT, __VA_ARGS__)) \
    :(getID == 3) ? overload_3(GET_ARG(0, CHAR_P, __VA_ARGS__), GET_ARG(1, INT, __VA_ARGS__), GET_ARG(2, CHAR_P, __VA_ARGS__)) \
    :static_error(__LINE__))

#define GET_ARG_TYPE(A) _Generic(((void)0, (A)), int: INT, char*: CHAR_P),
#define GET_ARG(N, T, ...) GET_ARG_DEFAULT_##T(M_GET_ELEM(N, __VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0))
#define GET_ARG_DEFAULT_INT(A) _Generic((A), int: (A), default: 0)
#define GET_ARG_DEFAULT_CHAR_P(A) _Generic(((void)0, (A)), char*: (A), default: NULL)


// metaprogramming utility macros (not directly related to this
#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N

#define M_CONC(A, B) M_CONC_(A, B)
#define M_CONC_(A, B) A##B

#define M_FOR_EACH(ACTN, ...) M_CONC(M_FOR_EACH_, M_NARGS(__VA_ARGS__)) (ACTN, __VA_ARGS__)
#define M_FOR_EACH_0(ACTN, E) E
#define M_FOR_EACH_1(ACTN, E) ACTN(E)
#define M_FOR_EACH_2(ACTN, E, ...) ACTN(E) M_FOR_EACH_1(ACTN, __VA_ARGS__)
#define M_FOR_EACH_3(ACTN, E, ...) ACTN(E) M_FOR_EACH_2(ACTN, __VA_ARGS__)
#define M_FOR_EACH_4(ACTN, E, ...) ACTN(E) M_FOR_EACH_3(ACTN, __VA_ARGS__)
#define M_FOR_EACH_5(ACTN, E, ...) ACTN(E) M_FOR_EACH_4(ACTN, __VA_ARGS__)

#define M_GET_ELEM(N, ...) M_CONC(M_GET_ELEM_, N)(__VA_ARGS__)
#define M_GET_ELEM_0(_0, ...) _0
#define M_GET_ELEM_1(_0, _1, ...) _1
#define M_GET_ELEM_2(_0, _1, _2, ...) _2
#define M_GET_ELEM_3(_0, _1, _2, _3, ...) _3
#define M_GET_ELEM_4(_0, _1, _2, _3, _4, ...) _4
#define M_GET_ELEM_5(_0, _1, _2, _3, _4, _5, ...) _5
// (end of utility stuff)


int main(void) {
    overload(1, 2, 3);            // prints "all ints (1, 2, 3)"
    overload(1, "two", 3);        // prints "b is a string (1, two, 3)"
    overload("one", 2, "three");  // prints "a and c are strings (one, 2, three)"
}

(M_NARGS, M_FOR_EACH and M_GET_ELEM are utility macros... you can extend them for more arguments easily, but they aren't directly connected to this.)

The way this works is to build a big ternary-operator conditional expression that contains all possible specializations for the function. We use the GET_ARG macro for each argument passed to a specialization, to choose using _Generic whether to supply an actual argument (if it's the right type for this branch), or a suitable default replacement (if this is the wrong one, in which case it will just go unused). _Generic is also mapped over all arguments using M_FOR_EACH to build a "runtime" array of type-id integers. This array, plus the number of arguments, is passed to get_overload_id to get the integer ID of the function we actually want to call, for use as a controlling expression in the big ternary expression.

Despite using runtime-level C constructs (a big ternary with all variations, a dispatch function to control it), this actually doesn't have any real runtime cost: since the arguments to the dispatch function are constant and it itself is static inline, GCC (and presumably any other half-decent compiler) can completely inline it and optimise out all of the unused branches of the big ternary, leaving only the specialization we actually want in the generated assembly (you can compile with gcc -S and see that this is the case). It is effectively a completely compile-time operation.




回答2:


There is no way to do this. By the time preprocessor does the macro expansion, all parameters are treated as text. Compiler hasn't even started analyzing the C code, so types don't even exist yet.

Only way to make it work is to use explicit type parameter:

#define TRACE(Id, type_branch, formatString,...)


来源:https://stackoverflow.com/questions/24366820/determine-argument-type-from-va-args-in-compile-time

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