Разрешение пользовательского макроса в #ifdef check - PullRequest
3 голосов
/ 26 июня 2019

Представьте, что у меня есть некоторая функциональность типа assert, которая объявляет макрос одним способом, если определенный макрос определен, и другим способом, если это не так:

// in some header assert2.hpp

#ifdef NO_ASSERT2
#define assert2(x)
#else
#define assert2(x) assert2_handler(x);
#endif

Здесь макрос NO_ASSERT2 очень похож на макрос NDEBUG в стандартном assert (3) .

Однако я хотел бы позволить пользователю переопределить проверку NO_ASSERT2 своим собственным макросом, прежде чем включать файл. Например, если вы включили assert2.hpp вот так:

#define NO_ASSERT_KEY NO_ASSERT_CUSTOM
#include "assert2.hpp"

Тогда макрос NO_ASSERT_CUSTOM будет проверен вместо значения по умолчанию NO_ASSERT2 для этой единицы перевода.

Это не должно работать точно так же, как описано выше - мне просто нужен какой-то способ переопределить поведение для каждого файла, не требуя больше, чем 1 строка шаблона, показанная выше в месте включения.

1 Ответ

1 голос
/ 26 июня 2019

Это не красиво ... но этот подход может работать для вас. Предполагается, что макрос определен с использованием #define FOO, #define FOO 1 или -DFOO (предполагается, что, как правило, это создает нечто, эквивалентное #define FOO 1).

#define SECOND(...) SECOND_I(__VA_ARGS__,,)
#define SECOND_I(A,B,...) B
#define GLUE3(A,B,C) GLUE3_I(A,B,C)
#define GLUE3_I(A,B,C) A##B##C
#define AGLUE3(A,B,C) AGLUE3_I(A,B,C)
#define AGLUE3_I(A,B,C) A##B##C
#define TEST_ASSERT_KEY GLUE3(NO_ASSERT_PROBE,0_,NO_ASSERT_KEY)
#define NO_ASSERT_PROBE0_NO_ASSERT_KEY AGLUE3(NO_ASSERT_PROBE,0_,NO_ASSERT2)
#define NO_ASSERT_PROBE0_  ,1
#define NO_ASSERT_PROBE0_1 ,1
#define NO_ASSERT_TEST SECOND(TEST_ASSERT_KEY,0)

При этом ваше использование будет:

#if NO_ASSERT_TEST 
#define assert2(x)
#else
#define assert2(x) assert2_handler(x);
#endif

Вот демоверсия на с накоплением кривоватых .

Используется сопоставление с образцом в препроцессоре через косвенный макрос SECOND. Идея состоит в том, что он расширяется до второго аргумента, но только косвенно ... это позволяет вам построить в качестве первого аргумента шаблон. Обычно этот первый аргумент игнорируется, но если вы хотите сопоставить что-либо, вы можете установить его так, чтобы первым аргументом был макрос, который расширяется запятой; это сдвигает новый второй аргумент, заменяя значение по умолчанию.

Отсюда легче объяснить задом наперед. NO_ASSERT_TEST использует TEST_ASSERT_KEY для построения шаблона со значением по умолчанию 0. TEST_ASSERT_KEY сборок NO_ASSERT_PROBE0_, связанных с NO_ASSERT_KEY. Когда определено NO_ASSERT_KEY, это создаст NO_ASSERT_PROBE0_, объединенный с расширением того, что он определил. В противном случае он перестраивает тестовый токен, используя NO_ASSERT_PROBE0_, объединенный с NO_ASSERT2.

В любом случае это косвенная вставка, поэтому NO_ASSERT_KEY в первом случае или NO_ASSERT2 во втором расширяется первым. В первом случае, если, скажем, NO_ASSERT_KEY равно NO_ASSERT_CUSTOM и NO_ASSERT_CUSTOM не определено, это создает NO_ASSERT_PROBE0_NO_ASSERT_CUSTOM, который является обычным идентификатором, который будет игнорироваться, что приведет к 0 из-за SECOND в NO_ASSERT_TEST. Но если NO_ASSERT_CUSTOM определено для #define NO_ASSERT_CUSTOM, то получается NO_ASSERT_PROBE0_, который расширяется до ,1, что переводит 1 в SECOND вызов в NO_ASSERT_TEST. Аналогично, если NO_ASSERT_CUSTOM определено для -DNO_ASSERT_CUSTOM в командной строке, это (как правило) сделает его определение эквивалентным #define NO_ASSERT_CUSTOM 1, что приведет к NO_ASSERT_PROBE0_1, который расширится до ,1.

Случаи, когда NO_ASSERT_KEY не определен, аналогичны.

Вероятно, есть более красивый способ построить эту конструкцию, если кто-то хочет сделать выстрел.

...