Развернуть макрос на основе его значения параметра

У меня есть макрос, который выглядит так:

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__) в значительной степени похожа на функцию, принимающую аргументы, но кто-то из комментаторов настаивает на этом.)