Сбой препроцессора из-за - '#' не сопровождается параметром макроса

Я пытаюсь упростить объявление массива, но столкнулся с проблемой с препроцессорами, которые я использую. Мой исходный код выглядит следующим образом:

#define REQ_ENTRY(parm_1, parm_2)    \
#if defined(parm_1)                  \
    { parm_1, parm_2 },              \
#endif

typedef struct {
    int parm1,
        parm2;
} MyTypedef_t;

static const MyTypedef_t MyList[] =
{
    REQ_ENTRY( ID_1, 1 )
    REQ_ENTRY( ID_2, 2 )
    REQ_ENTRY( ID_3, 3 )
    REQ_ENTRY( ID_4, 4 )
    REQ_ENTRY( ID_5, 5 )
};

Конечно, сборка завершается неудачно с сообщением об ошибке "error: '#' не сопровождается параметром макроса". Причина этого объясняется здесь ( почему компилятор жалуется на это объявление макроса)

По сути, я стараюсь не объявлять массив следующим образом, который работает:

static const MyTypedef_t MyList[] =
{
    #if defined (ID_1)
    { ID_1, 1 },
    #endif

    #if defined (ID_2)
    { ID_2, 2 },
    #endif

    #if defined (ID_3)
    { ID_3, 3 },
    #endif

    #if defined (ID_4)
    { ID_4, 4 },
    #endif

    #if defined (ID_5)
    { ID_5, 5 },
    #endif        
};

Список может быть довольно длинным и варьироваться в зависимости от типа сборки проекта. Я пытался подумать об использовании x-macros, но думаю, что у меня возникнет та же проблема. Я надеюсь, что кто-то может увидеть способ создания макроса препроцессора таким образом, чтобы я мог достичь исходного синтаксиса сахара? Любое понимание очень ценится.

3 ответа

Решение

Нет хорошего чистого решения. Но есть решения разного безобразия.

Если вы не возражаете, включив в определение макроса и идентификатор, и последовательность, это можно решить следующим образом:

#define CONCAT2(x,y) x##y
#define CONCAT(x,y) CONCAT2(x,y)
#define REQ_ENTRY_YES(p1, p2) { p1 , p2 }
#define REQ_ENTRY_NO(p1) 
#define IS_PAIR_HELPER(a, b, c, ...) c
#define IS_PAIR(...) IS_PAIR_HELPER(__VA_ARGS__, YES, NO)
#define REQ_ENTRY(pair) CONCAT(REQ_ENTRY_, IS_PAIR(pair))(pair)

#define ID_1 78723649, 1
#define ID_3 2347602, 3

typedef struct {
    int parm1,
        parm2;
} MyTypedef_t;

static const MyTypedef_t MyList[] =
{
    REQ_ENTRY( ID_1 )
    REQ_ENTRY( ID_2 )
    REQ_ENTRY( ID_3 )
    REQ_ENTRY( ID_4 )
    REQ_ENTRY( ID_5 )
};

Запустить через gcc с -std=c11 -Wall -Eи показывая только MyList определение:

static const MyTypedef_t MyList[] =
{
    { 78723649 , 1 }

    { 2347602 , 3 }


};

Вы можете сделать то же самое, используя любое второе значение в #define ID_x макросы, пока они есть; реальные параметры могут быть добавлены к REQ_ENTRY, Но это требует дополнительного жонглирования.

Жаль что defined Оператор доступен только в контексте #if а также #ifelse, но не для расширений макросов. В настоящее время я согласен с rici в отношении решений разного безобразия.

Вот решение, которое требует, чтобы определенные значения были заключены в скобки. Затем вы можете использовать идентификатор в качестве обычного значения и передать его DEF, который расширяется либо до 1, когда макрос находится в скобках, либо до 0, если нет. (Это уловка, которую я узнал здесь.)

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

/* Auxiliary macros */

#define M_CHECK(...) M_CHECK_(__VA_ARGS__)
#define M_CHECK_(a, b, ...) b

#define M_IS_PAREN(x) M_CHECK(M_IS_PAREN_ x, 0)
#define M_IS_PAREN_(...) 1, 1

#define M_CONCAT(a, b) M_CONCAT_(a, b)
#define M_CONCAT_(a, b) a ## b

/* Conditional definition macros */

#define DEF(x) M_IS_PAREN(x)

#define DEF_IF_0(id, def)
#define DEF_IF_1(id, def) {id, def},

#define COND_DEF(x, y) M_CONCAT(DEF_IF_, DEF(x))(x, y)

/* Implementation */

#define ID_1 (27)
#define ID_3 (28)
#define ID_4 (29)

static const MyTypedef_t MyList[] = {
    COND_DEF(ID_1, 1)
    COND_DEF(ID_2, 2)
    COND_DEF(ID_3, 3)
    COND_DEF(ID_4, 4)
    COND_DEF(ID_5, 5)
};

Это даст:

static const MyTypedef_t MyList[] = {
    {(27), 1},

    {(28), 3},
    {(29), 4},

};

Вы также можете использовать DEF макрос в коде, который будет расширен до 0 или 1:

printf("ID_1 is %s.\n", DEF(ID_1) ? "defined" : "undefined");

Это самое близкое, что я смог получить без введения избыточности:

      #define PREPROCESSOR_IF #if
#define PREPROCESSOR_ENDIF #endif
#define PREPROCESSOR_NEWLINE /*
*/

#define REQ_ENTRY(parm_1, parm_2)                         \
PREPROCESSOR_IF defined(parm_1)      PREPROCESSOR_NEWLINE \
    { parm_1, parm_2 },              PREPROCESSOR_NEWLINE \
PREPROCESSOR_ENDIF

typedef struct {
    int parm1,
        parm2;
} MyTypedef_t;

static const MyTypedef_t MyList[] =
{
    REQ_ENTRY( ID_1, 1 )
    REQ_ENTRY( ID_2, 2 )
    REQ_ENTRY( ID_3, 3 )
    REQ_ENTRY( ID_4, 4 )
    REQ_ENTRY( ID_5, 5 )
};

Вам нужно запустить препроцессор только один раз, используя -Eа также -CCа затем скомпилировать результат первого прохода препроцессора, однако он не работает из-за (например)

      #if defined (ID_1) /*
*/ { ID_1, 1 }, /*
*/ #endif

не распознаются препроцессором как отдельные строки, поскольку комментарии заменяются только одним пробелом.

Я смог придумать решение с использованием избыточности, но вряд ли оно лучше того, что вы предложили (каждый раз выписывая весь оператор):

      #define PREPROCESSOR_IF #if
#define PREPROCESSOR_ENDIF #endif
#define PREPROCESSOR_DEFINE #define
#define PREPROCESSOR_NEWLINE /*
*/

#define REQ_ENTRY_1(parm_1, parm_2) PREPROCESSOR_IF defined(parm_1)
#define REQ_ENTRY_2(parm_1, parm_2) { parm_1, parm_2 }, 
#define REQ_ENTRY_3(parm_1, parm_2) PREPROCESSOR_ENDIF

PREPROCESSOR_DEFINE ID_1 (27)
PREPROCESSOR_DEFINE ID_3 (28)
PREPROCESSOR_DEFINE ID_4 (29)

typedef struct {
    int parm1,
        parm2;
} MyTypedef_t;

static const MyTypedef_t MyList[] =
{
    REQ_ENTRY_1( ID_1, 1 )
    REQ_ENTRY_2( ID_1, 1 )
    REQ_ENTRY_3( ID_1, 1 )

    REQ_ENTRY_1( ID_2, 2 )
    REQ_ENTRY_2( ID_2, 2 )
    REQ_ENTRY_3( ID_2, 2 )

    REQ_ENTRY_1( ID_3, 3 )
    REQ_ENTRY_2( ID_3, 3 )
    REQ_ENTRY_3( ID_3, 3 )

    REQ_ENTRY_1( ID_4, 4 )
    REQ_ENTRY_2( ID_4, 4 )
    REQ_ENTRY_3( ID_4, 4 )

    REQ_ENTRY_1( ID_5, 5 )
    REQ_ENTRY_2( ID_5, 5 )
    REQ_ENTRY_3( ID_5, 5 )
};

Это компилируется по желанию с использованием двухэтапного процесса компиляции, описанного выше.

Другие вопросы по тегам