Как може макрос да дефинира валидно глобално име въз основа на типа, който му е предаден?

Вярвам, че заглавието се обяснява само по себе си, но ето един пример, който да илюстрира какво се опитвам да постигна:

#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(...) и token-paste всички __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