Развернуть макрос на основе его значения параметра
У меня есть макрос, который выглядит так:
M(id,...)
Я хотел бы, чтобы он расширялся до нуля, если id == 0
и что-то еще в противном случае.
Возможно ли это? Если да, то как?
Мой первый инстинкт заключался в том, чтобы попробовать что-то вроде этого:
#define M(id,...) M##id(__VA_ARGS__)
#define M0(...)
#define M_NOT_0(...) some_code(__VA_ARGS__)
Но последняя строка здесь явно недействительна, и я не могу понять, как заставить этот шаблон работать.
Примечания:
-
id
- целое число от 0 до 255, но в идеале я бы хотел избежать создания 256 отдельных макроопределений.
- Пожалуйста, не оспаривайте предпосылку вопроса. Макрос
M(id,...)
не может быть изменен.
- Никакие предположения о расширении окончательного кода могут быть сделаны.
Ответы
Ответ 1
Макрос CHECK0 работает на основе количества токенов, встречающихся в макросе.
Если токен, переданный макросу, имеет значение 0, он расширяется до HIDDEN0, который расширяется до двух токенов. В противном случае он расширяется до макроса, который не существует, и этот макрос считается одним токеном.
Затем макрос SECOND выбирает второй токен. Если встречается HIDDEN0, он выбирает 0, если встречается несуществующий токен, он выбирает 1.
Затем этот результат объединяется с префиксом, выбранным пользователем. Это видно в макросах HELLO и PRINT. Один - простой макрос, другой - макрофункция. Получающийся макрос либо HELLO0, либо HELLO1. Один из них определяется как нечто полезное, другое определяется как пустое. То же самое касается PRINT.
#include <stdlib.h>
#include <stdio.h>
#define EXPAND(...) __VA_ARGS__ //needed for MSVC compatibility
#define JOIN_EXPAND( a , b ) a##b
#define JOIN( a , b ) JOIN_EXPAND( a , b )
#define SECOND_EXPAND( a , b , ... ) b
#define SECOND(...) EXPAND( SECOND_EXPAND( __VA_ARGS__ ) )
#define HIDDEN0 unused,0
#define CHECK0( value ) SECOND( JOIN( HIDDEN , value ) , 1 , unused )
#define HELLO0 puts( "HELLO" )
#define HELLO1
#define HELLO( value ) JOIN( HELLO , CHECK0( value ) )
#define PRINT0( ... ) printf( __VA_ARGS__ )
#define PRINT1( ... )
#define PRINT( value , ... ) JOIN( PRINT , CHECK0( value ) )( __VA_ARGS__ )
int main( void )
{
HELLO( 54545 ) ; //evaluates to nothing
HELLO( 1 ) ; //evaluates to nothing
HELLO( 0 ) ; //evaluates to puts( "HELLO" )
PRINT( 861151 , "Some values: %lf %d\n" , 3.13 , 12345 ) ; //evaluates to nothing
PRINT( 1 , "Some values: %lf %d\n" , 3.13 , 12345 ) ; //evaluates to nothing
PRINT( 0 , "Some values: %lf %d\n" , 3.13 , 12345 ) ; //evaluates to printf( "Som
printf( "%d %d %d\n", CHECK0( 0 ) , CHECK0( 1 ) , CHECK0( 123456 ) ) ; //outputs 0 1 1
return EXIT_SUCCESS ;
}
Ответ 2
Основанный почти полностью на @PaulFultzII великолепно подробный ответ на этот вопрос, вот способ сделать это:
#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 CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1
#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0
#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 PROBE(~)
#define BOOL(x) COMPL(NOT(x))
#define IF(c) IIF(BOOL(c))
#define EAT(...)
#define EXPAND(id, ...) printf(__VA_ARGS__, id)
#define M(id, ...) IF(id)(EXPAND(id, __VA_ARGS__), EAT(id, __VA_ARGS__))
M(0, "don't ever print this!\n")
M(1, "ok to print %s, %d\n", "with a string")
M(172, "ok to print %d\n")
Если мы запустим это через препроцессор (cpp
в случае компилятора GNU C), мы получим следующее:
printf("ok to print %s, %d\n", "with a string", 1)
printf("ok to print %d\n", 172)
Ответ 3
Предполагая some_code()
на самом деле код
Это может быть сложно: если id
расширяется из другого токена, это может быть чем-то вроде (0)
вместо 0
или 0x10
вместо 16
. Кроме того, значения времени выполнения.
Если у вас есть достаточно современный компилятор, вы можете использовать устранение мертвого кода:
#define M(id, ...) do { \
if (id) { \
some_code(__VA_ARGS__); \
} \
} while (/* CONSTCOND */ 0)
Это сделает трюк даже в настройках, таких как...
void
foo(int bar, int baz) {
M(bar, baz);
}
... и, поскольку id
предполагается постоянным во всех других случаях, if
будет оптимизирован (даже GCC 3.4.6 без каких-либо флагов оптимизации делает это для значений нуля и nōn-zero, Я просто проверил: LLVM также это сделает, и многие коммерческие компиляторы Unix-компиляторов, вероятно, сделают это, PCC, вероятно, не будет, но вряд ли встретит вас).
Итак, это не решение pure-cpp, но одно из них, вероятно, работает в дикой природе, и оно не вызывает Undefined Поведение или подобное... весело... также.
/* CONSTCOND */
предназначен для lint, а весь блок do { … } while (0)
обычно используется вокруг макросов, которые содержат конструкторы управления, поэтому их можно использовать повсеместно.
Если some_code
не является кодом, а является арифметическим выражением
В этом случае одно и то же решение становится еще короче:
#define M(id, ...) ((id) ? some_code(__VA_ARGS__) : void)
Вместо void
в конце замените все, что вы хотите вернуть, если id
равно нулю. (Я не верю, что вопрос требует этого, поскольку запись some_code(__VA_ARGS__)
в значительной степени похожа на функцию, принимающую аргументы, но кто-то из комментаторов настаивает на этом.)