C-препроцессорная макроспециализация на основе аргумента

Возможно ли, чтобы один макрос расширялся по-разному для одного определенного значения аргумента и по-разному для всех остальных аргументов?

Скажем, я определяю текущего пользователя:

#define CURRENT_USER john_smith

То, что я хочу сделать, - это иметь макрос, который будет расширяться по-разному, если пользователь передает совпадения CURRENT_USER. Имейте в виду, что я не знаю всех возможных пользователей априори. Самый простой случай:

#define IS_CURRENT_USER(user)                   \
    /* this is not valid preprocessor macro */  \
    #if user == CURRENT_USER                    \
        1                                       \
    #else                                       \
        0                                       \
    #endif                                      

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

#define SOME_USER_SPECIFIC_MACRO(user) SOME_USER_SPECIFIC_MACRO_SWITCH_1(IS_CURRENT_USER(user))

#define SOME_USER_SPECIFIC_MACRO_SWITCH_1(switch)   SOME_USER_SPECIFIC_MACRO_SWITCH_2(switch) // expand switch ...
#define SOME_USER_SPECIFIC_MACRO_SWITCH_2(switch)   SOME_USER_SPECIFIC_MACRO_##switch         // ... and select specific case

#define SOME_USER_SPECIFIC_MACRO_0  ... // not current user
#define SOME_USER_SPECIFIC_MACRO_1  ... // current user

Возможно ли это?

EDIT: Позвольте мне пояснить. Скажем, каждый программист определяет разные CURRENT_USER в своем заголовке конфигурации. Я хочу, чтобы макросы, специфичные для пользователя, использовали exan для чего-то значимого, если и только если их аргумент user соответствует CURRENT_USER. Поскольку я хотел бы, чтобы эти макросы содержали _pragma, это не может быть проверка времени выполнения (как предложено в некоторых андерсерах ниже).

EDIT: Опять же, разъяснение. Скажем, макрос отключает оптимизацию некоторых разделов кода:

#define TURN_OPTIMISATION_OFF __pragma optimize("", off)

Некоторые программисты хотят отключить оптимизацию для разных разделов кода, но не все за один раз. Я хотел бы иметь макрос:

#define TURN_OPTIMISATION_OFF(user) /* magic */

Это будет соответствовать аргументу user против макроса CURRENT_USER, взятому из конфигурационного файла для каждого программиста. Если пользователь сопоставляет макрос, он расширяется в прагму. Если нет, то ничего.

Ответ 1

Отключает можно. Этот anwser основан на макросах Pauls, но намного проще и не требует определения для каждого пользователя.

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define PROBE(x) x, 1 

Теперь, из-за ошибки MSVC мне пришлось немного изменить макрос CHECK.

#define MSVC_VA_ARGS_WORKAROUND(define, args) define args
#define CHECK(...) MSVC_VA_ARGS_WORKAROUND(CHECK_N, (__VA_ARGS__, 0))
#define CHECK_N(x, n, ...) n

Вместо определения CURRENT_USER я переключился на следующие макросы.

#define ENABLE_USER_gwiazdorrr () // gwiazdorrr is now enabled
#define ENABLE_USER_foo ()        // foo is also enabled
// #define ENABLE_USER_bar ()     // bar is NOT enabled

Это на самом деле дает больше гибкости, потому что можно одновременно включить нескольких пользователей. Скобки необходимы. Макросы ниже фактически обнаруживают, расширяется ли ENABLE_USER_<user> в скобки или нет.

#define USER_ENABLED_PROBE(user)            USER_ENABLED_PROBE_PROXY( ENABLE_USER_##user ) // concatenate prefix with user name
#define USER_ENABLED_PROBE_PROXY(...)       USER_ENABLED_PROBE_PRIMIVIE(__VA_ARGS__)       // expand arguments
#define USER_ENABLED_PROBE_PRIMIVIE(x)      USER_ENABLED_PROBE_COMBINE_##x                 // merge
#define USER_ENABLED_PROBE_COMBINE_(...)    PROBE(~)                                       // if merge successful, expand to probe

USER_ENABLED_PROBE(gwiazdorrr) // expands to ~, 1
USER_ENABLED_PROBE(bar)        // expands to USER_ENABLED_PROBE_COMBINE_bar

Теперь это детская игра:

#define IS_USER_ENABLED(user) CHECK(USER_ENABLED_PROBE(user))

IS_USER_ENABLED(gwiazdorrr)   // expands to 1
IS_USER_ENABLED(bar)          // expands to 0

Имея этот макрос и IIF (спасибо Пол!), я решил реализовать макрос оптимизации, упомянутый в исходном вопросе:

#define TURN_OPTIMISATION_OFF(user) IIF( IS_USER_ENABLED(user) ) \
    (\
        __pragma optimize("", off),\
        /* nothing */ \
    )

TURN_OPTIMISATION_OFF(gwiazdorrr) // expands into __pragma optimize("", off)
TURN_OPTIMISATION_OFF(foo)        // expands into __pragma optimize("", off)
TURN_OPTIMISATION_OFF(bar)        // nothing emitted

Спасибо за ввод!

EDIT: здесь версия GCC: http://ideone.com/129eo

Ответ 2

Ну, во-первых, вы можете выполнить сопоставление с шаблоном с помощью препроцессора, используя ##. Вот как можно определить макрос IIF:

#define IIF(cond) IIF_ ## cond
#define IIF_0(t, f) f
#define IIF_1(t, f) t

Однако у этого подхода есть одна проблема. Тонкий побочный эффект оператора ## заключается в том, что он запрещает расширение. Вот пример:

#define A() 1
//This correctly expands to true
IIF(1)(true, false) 
// This will however expand to IIF_A()(true, false)
// This is because A() does not expand to 1,
// because its inhibited by the ## operator
IIF(A())(true, false) 

Чтобы обойти это, используйте другое косвенное обращение. Поскольку это обычно делается, мы можем написать макрос под названием CAT, который будет объединяться без торможения.

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

Итак, теперь мы можем написать макрос IIF:

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define A() 1
//This correctly expands to true
IIF(1)(true, false) 
// And this will also now correctly expand to true
IIF(A())(true, false)

С помощью сопоставления с образцом мы можем определить другие операции, такие как COMPL, который принимает дополнение:

// A complement operator
#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0
// An and operator
#define BITAND(x) PRIMITIVE_CAT(BITAND_, x)
#define BITAND_0(y) 0
#define BITAND_1(y) y

Далее, методы обнаружения могут использоваться для определения, является ли параметр определенным значением или является скобкой. Он опирается на переменные аргументы, расширяющиеся до различного числа параметров. В основе обнаружения лежит макрос CHECK с макросом PROBE, подобным следующему:

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1,

Это очень просто. Когда зонд передается макросу CHECK следующим образом:

CHECK(PROBE(~)) // Expands to 1

Но если мы дадим ему один токен:

CHECK(xxx) // Expands to 0

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

#define IS_PAREN(x) CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_PROBE(...) PROBE(~)
IS_PAREN(()) // Expands to 1
IS_PAREN(xxx) // Expands to 0

Далее нам нужно сравнить два токена, мы можем полагаться на то, что макросы не расширяются рекурсивно. Мы заставляем макрос расширяться рекурсивно внутри другого макроса. Если два токена одинаковы, то они будут рекурсивно расширять макросы, что мы обнаружим, попытавшись определить, расширились ли они в скобки или нет, вот макрос COMPARE:

#define COMPARE(a, b) PRIMITIVE_COMPARE(a, b)
#define PRIMITIVE_COMPARE(a, b) \
    IIF( \
        BITAND \
            (IS_PAREN(COMPARE_ ## a(()))) \
            (IS_PAREN(COMPARE_ ## b(()))) \
    )( \
        COMPL(IS_PAREN( \
            COMPARE_ ## a( \
                COMPARE_ ## b \
            )(()) \
        )), \
        0 \
    ) \

Каждый токен, который вы хотите сравнить, вы бы определили так:

// So you would define one for each user
#define COMPARE_john_smith(x) x
#define COMPARE_another_user_name(x) x

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

#define MACRO_CURRENT_USER(user) ...
#define MACRO_OTHER_USER(user) ...

Тогда вы можете написать что-то вроде этого:

// Detects if its the current user
#define IS_CURRENT_USER(user) COMPARE(user, CURRENT_USER)
// Your macro
#define MACRO(user) IIF(IS_CURRENT_USER(user), MACRO_CURRENT_USER, MACRO_OTHER_USER)(user)

Ответ 3

Если аргумент макроса всегда очень постоянен (даже в прямом и буквальном смысле), вы можете использовать трюки с конкатенацией, что-то вроде

#define SOME_MACRO(T) SOME_MACRO_FOR_##T
#define SOME_MACRO_FOR_0 somethinghere()
#define SOME_MACRO_FOR_1 somethingelse()

В противном случае вы могли бы иметь

#define CURRENT_USER ((user == THE_USER)?(something()):(somethingelse()))

Или используйте static inline крошечную функцию:

static inline int current_user(int user)
{
   return (user==THE_USER)?(something()):(somethingelse());
}

(Обратите внимание, что если user является константой, возможно, после некоторой предыдущей оптимизации компилятора, компилятор оптимизирует ее до чего-то более простого, и скомпилированный двоичный файл не будет тестировать user во время выполнения. См. Также __builtin_constant_p при компиляции с помощью gcc).

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

И вы не сказали нам, каково ваше точное использование макроса. Вы используете это как lvalue?

Как вы говорите, препроцессор не может расширяться до директивы препроцессора, поэтому ваш пример:

#define IS_CURRENT_USER(user)                   \
  /* this is not valid preprocessor macro */  \
  #if user == CURRENT_USER                    \
      1                                       \
  #else                                       \
      0                                       \
  #endif       

(как вы говорите) неверно.

Вам разрешено делать только такие вещи, как:

 #if user == CURRENT_USER
 #define IS_CURRENT_USER(U) 1
 #else
 #define IS_CURRENT_USER(u) 0
 #endif

Я сознательно использую u не user как формальный аргумент вашего макроса IS_CURRENT_USER для удобства чтения (этот формальный IS_CURRENT_USER, только его вхождения в макросе).

Вы понимаете, что предварительная обработка происходит "до" компиляции? Вы запускали, например, gcc -C -E чтобы получить предварительно обработанный вывод? Это должно быть поучительно!

Узнайте больше о препроцессоре C

Кстати, вы рассматривали возможность создания некоторого кода на C (возможно, где-то #include -d) с помощью скрипта (или вашего собственного генератора, или автоинструментов, или универсального препроцессора, такого как autogen или m4)? Вы можете сгенерировать (например, из пользовательской базы, такой как /etc/passwd в Linux, или NIS/YP, LDAP или с помощью getpwent(3)...) #include -d myoptim.h с такими вещами, как

#if CURRENT_USER_ID==1234
#define OPTIMIZATION_FOR_PAUL _pragma(GCC(optimize,"-O1"))
#else
#define OPTIMIZATION_FOR_PAUL /*nothing*/
#endif
#if CURRENT_USER_ID==3456
#define OPTIMIZATION_FOR_ALICE _pragma(GCC(optimize,"-O1"))
#else
#define OPTIMIZATION_FOR_ALICE /*nothing*/
#endif

и спросить Павла (предполагая его UID 1234) префикс его функции OPTIMIZATION_FOR_PAUL и положить CFLAGS=-dCURRENT_USER_ID=$(shell id -u) в вашем Makefile; Я нахожу это уродливым (и это не учитывает тот факт, что оптимизация может изменить глобальное поведение плохо написанных программ -C).

Вы можете настроить GCC, например, с помощью расширения MELT, предоставляющего пользовательскую прагму или встроенную для ваших нужд, но я нахожу это странным в вашем конкретном случае.

NB. Исторически, cpp был разработан, чтобы быть быстрым текстовым процессором, а не полным -C. В старые времена (Unix 1980-х годов) он работал как отдельный процесс, с реальной компиляцией, выполняемой cc1, а компилятор cc был просто сценарием оболочки, управляющим ими (с as и ld). Сегодня gcc - это небольшая программа-драйвер, но cc1 включает в себя препроцессор по соображениям производительности. Тем не менее, стандарт C указан так, что предварительная обработка может быть отдельной программой от самого компилятора.

Ответ 4

Перед компиляцией выполняется предварительная обработка.

Если пользователь известен препроцессору, тогда да:

#define user 4
#define CURRENT_USER 4
#define IS_CURRENT_USER 1

#if user == CURRENT_USER
#define IS_CURRENT_USER(user) 1
#else
#define IS_CURRENT_USER(user) 0
#endif

Но это совершенно бесполезно, и я сомневаюсь в том, что у вас на самом деле есть.

В противном случае нет. Не злоупотребляйте mecros и препроцессором.

После редактирования:

Нет, то, что вы хотите, определенно невозможно (отключите оптимизацию в зависимости от пользователя).

Ответ 5

Код ниже не чувствителен к ошибке MSVC. Аргументы... не разделены.

#define IF_USER_ENABLED(x,...) IF_USER_ARGS_GT2 (ENABLE_USER_ ## x,__VA_ARGS__) 
#define IF_USER_ARGS_GT2(x,...) ARGS_ARG2 (x,GT4,3,,__VA_ARGS__)
#define ARGS_ARG2(x,y,z,...) ARGS_ ## z (x,y,z,__VA_ARGS__)
#define ARGS_3(x,y,z,w,...) w
#define ARGS_GT4(x,y,z,w,v,...) __VA_ARGS__

#define IF_USER_DISABLED(x,...) IF_NOT_USER_ARGS_GT2 (ENABLE_USER_ ## x,__VA_ARGS__) 
#define IF_NOT_USER_ARGS_GT2(x,...) ARGS_ARG2 (x,4,GT3,,__VA_ARGS__)
#define ARGS_4(x,y,z,w,v,...) v
#define ARGS_GT3(x,y,z,w,...) __VA_ARGS__

#define ENABLE_USER_foo ,
//#define ENABLE_USER_bar ,

Ответ 6

Почему вы не использовали простое утверждение if? #if нельзя использовать в макросе.

Вот пример:

// Return 1 if 'user' is the current user, 0 else.
#define IS_CURRENT_USER(user) ((user) == CURRENT_USER)

Или, если вы установили USER во время компиляции, вы можете уменьшить количество условных ветвей.

#if USER == CURRENT_USER
# define IS_CURRENT_USER (1)
#else
# define IS_CURRENT_USER (0)
#endif

Ответ 7

Что случилось с чем-то подобным?

#if CURRENT_USER == john_smith
    #define SOME_USER_SPECIFIC_MACRO_SWITCH  WHATEVER ## CURRENT_USER
    #define SOME_USER_SPECIFIC_MACRO_1  ... 
#else
    #define SOME_USER_SPECIFIC_MACRO_0  ... 
    #define SOME_USER_SPECIFIC_MACRO_SWITCH  WHATEVER ## Somethingelse
#endif// not current user