在编译时从__VA_ARGS__确定参数类型

时间:2014-06-23 13:10:21

标签: c macros compilation trace variadic-functions

我希望确定使用 VA_ARGS 传递给函数的参数类型,以便将其路由到正确的处理程序,但是在编译时(而不是在具有va_args()的函数内)

通过确定类型我的意思是我需要知道跟踪是否只包含整数或其中包含字符串,但我希望它将在编译时。

例如:

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

任何想法?

谢谢!

2 个答案:

答案 0 :(得分:2)

您可以使用_Generic运算符对表达式的类型进行编译时调度。请注意,这是主要C语言的一部分,而不是预处理器宏。

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

作为_Generic的第一个参数提供的表达式仅在编译时用于其类型,以选择内联值(在本例中为传递运行时变量的函数)。 / p>


_Generic仅适用于单个参数,因此大多数示例仅显示如何使用单个参数重载函数。您可以进行一些大量的元编程来创建深度嵌套的_Generic树,这些树可以通过所有传递的参数来咀嚼,但是这里有一个更简单的方法来重载具有多个类型的多个参数的函数:

#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_NARGSM_FOR_EACHM_GET_ELEM是实用程序宏...您可以轻松扩展它们以获得更多参数,但它们并不直接与此相关联。)

这种方法的工作方式是构建一个大的三元运算符条件表达式,其中包含函数的所有可能的特化。我们对传递给特化的每个参数使用GET_ARG宏,选择使用_Generic是否提供实际参数(如果它是该分支的正确类型),或者是合适的默认替换(如果是是错误的,在这种情况下它将不会使用)。 _Generic也使用M_FOR_EACH映射到所有参数,以构建type-id整数的“运行时”数组。这个数组加上参数的数量被传递给get_overload_id以获得我们实际想要调用的函数的整数ID,以用作大三元表达式中的控制表达式。

尽管使用了运行时级别的C构造(一个包含所有变量的大型三元组,一个用于控制它的调度函数),但它实际上没有任何实际的运行时成本:因为调度函数的参数是常量而它本身就是static inline,GCC(可能是任何其他一些不太好的编译器)可以完全内联它并优化大三元组的所有未使用的分支,只留下我们在生成的程序集中实际需要的特化(你可以用gcc -S并且看到情况就是这样)。它实际上是一个完全编译时的操作。

答案 1 :(得分:1)

没有办法做到这一点。到预处理器执行宏扩展时,所有参数都被视为文本。编译器甚至还没有开始分析C代码,因此类型甚至还不存在。

使其工作的唯一方法是使用显式类型参数:

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