Определить тип аргумента от __VA_ARGS__ во время компиляции

Я хочу определить типы параметров, переданных функции, используя 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__)

любые идеи?

спасибо!

Ответ 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_NARGS, M_FOR_EACH и M_GET_ELEM являются макросами утилиты... вы можете расширить их для большего удобства, но они напрямую не связаны с этим.)

Как это работает, нужно построить большое тернарное операторное выражение, содержащее всевозможные специализации для функции. Мы используем макрос GET_ARG для каждого аргумента, переданного специализации, чтобы выбрать с помощью _Generic указать фактический аргумент (если это правильный тип для этой ветки) или подходящую замену по умолчанию (если это неверно, и в этом случае он просто не будет использоваться). _Generic также отображается по всем аргументам с помощью M_FOR_EACH для построения массива "runtime" целых чисел типа. Этот массив, а также количество аргументов передается в get_overload_id, чтобы получить идентификатор целочисленной функции, которую мы действительно хотим вызвать, для использования в качестве управляющего выражения в большом тройном выражении.

Несмотря на использование конструктов C уровня времени выполнения (большой тройной со всеми вариантами, диспетчерская функция для его контроля), на самом деле это не имеет реальной стоимости исполнения: поскольку аргументы функции отправки являются постоянными, и это само по себе static inline, GCC (и, предположительно, любой другой наполовину достойный компилятор) может полностью встроить его и оптимизировать все неиспользуемые ветки большого тройника, оставив только ту специализацию, которую мы действительно хотим в сгенерированной сборке (вы можете скомпилировать с помощью gcc -S и посмотрите, что это так). Это фактически полностью компиляция.

Ответ 2

Нет никакого способа сделать это. К тому времени, когда препроцессор выполняет макрораспределение, все параметры обрабатываются как текст. Компилятор даже не начал анализировать код C, поэтому типы еще не существуют.

Единственный способ заставить его работать - использовать явный параметр типа:

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