Как макрос может определить допустимое глобальное имя на основе переданного ему типа?

Я считаю, что название говорит само за себя, но вот пример, иллюстрирующий то, что я пытаюсь сделать:

#define PASTE2(_0, _1)  _0 ## _1

#define DEFINE_OPS_FOR_TYPE(TYPE)                   \
    int PASTE2(do_something_with_, TYPE)(void) {    \
        /* do_something_with_<TYPE> */              \
    }

Все работает нормально для типов char, int и однословных типов, но когда дело доходит до типов unsigned или других, которые имеют несколько ключевых слов, использование вставки токена (a ## b) не создает действительное имя из-за пробела (например: do_something_with_foo bar) .

Самое простое решение, которое я мог придумать, - это изменить макрос DEFINE_OPS_FOR_TYPE, чтобы он использовал действительное имя в качестве второго параметра. Например:

#define DEFINE_OPS_FOR_TYPE(TYPE, NAME_FOR_TYPE)            \
    int PASTE2(do_something_with_, NAME_FOR_TYPE)(void) {   \
        /* do_something_with_<NAME_FOR_TYPE> */             \
    }

Это работает, как и ожидалось, но мне интересны другие возможные решения, даже если они слишком сложны. Я думал об использовании _Generic, но не вижу, как это поможет в определении имени.

Можете ли вы придумать другое решение?


person jweyrich    schedule 03.01.2013    source источник
comment
Я не знаю о C11, но, безусловно, до этого макросы не имели возможности выполнять сложную обработку строк (например, замену пробелов или символов, таких как *).   -  person Oliver Charlesworth    schedule 03.01.2013
comment
@OliCharlesworth Как вы думаете, вариативный макрос будет работать для DEFINE_OPS_FOR_TYPE(unsigned, int) и DEFINE_OPS_FOR_TYPE(unsinged, long, long)? Я думаю о #define DEFINE_OPS_FOR_TYPE(...) и токен-вставке всех __VARGS__ до 3 аргументов. Но я сомневаюсь, что это возможно.   -  person jweyrich    schedule 03.01.2013


Ответы (2)


На уровне объявления или определения символов, которые вы хотите сделать, не так уж много выходов из typedefing вещей до уникального идентификатора для рассматриваемого типа. _Generic или эквивалентные замены вступают в силу слишком поздно, чтобы быть полезными для препроцессора.

Но существует лишь конечное число стандартных типов, которые создают такую ​​проблему. Таким образом, вы можете легко придумать соглашение для определения типов.

Где _Generic может помочь, так это на стороне использования таких определенных символов. Здесь вы можете сделать что-то вроде

_Generic((X),
  unsigned long: do_something_with_ulong,
  unsigned char: do_something with_uchar,
  ...
)(X)

В P99 я следую этой схеме, и вы найдете для нее множество макросов поддержки.

person Jens Gustedt    schedule 03.01.2013

В итоге я использовал макрос с пустым аргументом. Пример:

#define STR2(x)             # x
#define STR(x)              STR2(x)
#define PASTE3(_1,_2,_3)    _1 ## _2 ## _3
#define FOO(_1,_2,_3)       PASTE3(_1, _2, _3)

printf("%s\n", STR(FOO(int,,)));
printf("%s\n", STR(FOO(unsigned, int,)));
printf("%s\n", STR(FOO(unsigned, long, long)));

Как вы можете увидеть здесь, вывод следующий:

int
unsignedint
unsignedlonglong

Я не помню, правильно ли определено использование пустых аргументов макроса в соответствии со стандартом, но я могу сказать вам, что Clang 3.1 не выдает никаких предупреждений для -std=c11 с -pedantic.

Вот код, если вы хотите попробовать:

#include <stdio.h>
#include <limits.h>

#define PASTE4(_1,_2,_3,_4) _1 ## _2 ## _3 ## _4

#define DEFINE_OPS_FOR_TYPE1(T1)        DEFINE_OPS_FOR_TYPE2(T1,)
#define DEFINE_OPS_FOR_TYPE2(T1, T2)    DEFINE_OPS_FOR_TYPE3(T1,T2,)
#define DEFINE_OPS_FOR_TYPE3(T1, T2, T3)                                    \
    int PASTE4(write_,T1,T2,T3)(FILE *file, void *data) {                   \
        T1 T2 T3 foo;                                                       \
        int written = fprintf(file, fmt_specifier(foo), *((T1 T2 T3 *)data));\
        return written > 0 ? 0 : -1;                                        \
    }

#define fmt_specifier(x)                \
    _Generic((x),                       \
        int: "%i",                      \
        unsigned int: "%u",             \
        unsigned long long: "%llu",     \
        default: NULL                   \
    )

DEFINE_OPS_FOR_TYPE1(int)
DEFINE_OPS_FOR_TYPE2(unsigned, int)
DEFINE_OPS_FOR_TYPE3(unsigned, long, long)

int main() {
    int var_int = INT_MAX;
    write_int(stdout, &var_int);
    printf("\n");
    unsigned int var_uint = UINT_MAX;
    write_unsignedint(stdout, &var_uint);
    printf("\n");
    unsigned long long var_ullong = ULLONG_MAX;
    write_unsignedlonglong(stdout, &var_ullong);
    printf("\n");
    return 0
}
person jweyrich    schedule 03.01.2013
comment
пустые аргументы действительно хорошо определены, с этим проблем нет - person Jens Gustedt; 03.01.2013
comment
@JensGustedt: Отлично! Благодарим вас за вклад. - person jweyrich; 03.01.2013
comment
Вы можете улучшить это с помощью таких приемов, как VA_NARGS, который является популярным способом подсчета количества аргументов в макросе. - person Morwenn; 08.02.2013
comment
@Morwenn: Спасибо за заметку. Приведенный выше код является просто демонстрацией. Я вырезал много линий, чтобы они хорошо вписались сюда. В настоящем используется более простая вариация PP_NARG — слава @JensGustedt и его P99. - person jweyrich; 08.02.2013