【问题标题】:determine argument type from __VA_ARGS__ in compile time在编译时从 __VA_ARGS__ 确定参数类型
【发布时间】:2014-06-23 13:10:21
【问题描述】:

我希望使用 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__)

有什么想法吗?

谢谢!

【问题讨论】:

    标签: c macros compilation trace variadic-functions


    【解决方案1】:

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

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

    您作为_Generic 的第一个参数提供的表达式仅用于其类型,在编译时选择要内联的值(在这种情况下,是传递运行时变量的函数)。


    _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 映射到所有参数,以构建类型 ID 整数的“运行时”数组。这个数组,加上参数的数量,被传递给get_overload_id 以获取我们实际要调用的函数的整数 ID,用作大三元表达式中的控制表达式。

    尽管使用了运行时级别的 C 构造(一个包含所有变体的大三元,一个调度函数来控制它),这实际上并没有任何实际的运行时成本:因为调度函数的参数是常量,而且它本身就是static inline,GCC(以及可能任何其他半体面的编译器)可以完全内联它并优化所有未使用的大三元分支,在生成的程序集中只留下我们真正想要的专业化(你可以用 @987654336 编译@ 并看到是这种情况)。它实际上是一个完全编译时的操作。

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-07
        • 1970-01-01
        • 1970-01-01
        • 2021-11-22
        • 2019-03-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多