Порядок оценки макросов #define

c++

EDIT: я думаю, что это лучше подходит для переполнения стека C++, поэтому я собираюсь повторно опубликовать это там.

Я работаю над системой меню с помощью макросов #define (на основе прошивки Marlin на моем 3D-принтере). Я пытаюсь обобщить каждый пункт меню с помощью макроса #define MENU_ITEM_EDIT( ). Полный код очень длинный, поэтому я опускаю куски кода для компактности. Однако вы можете найти полный код здесь: https://pastebin.com/h24itG9y

void draw_row(int row, char* value) { /* Stuff here*/ } // Рисует строку
char* uiToStr(int x) { /* Stuff here*/ } // Преобразует целое число без знака в строку

#define disp_array(display, value)  display[value]
#define disp_func(display, value)   display(value)
#define disp_char(display, value)   display

#define MENU_ITEM_EDIT(min, max, rate, display, displayType, width, label, value) \
    // Куча всего
    draw_row(rowNum, disp_ ## displayType(display, value)); \
    // Больше вещей

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

// Значения
const char item1[] = "Item 1", item2[] = "Item 2", item3[] = "Item 3";
const char* const itemList[] = {item1, item2, item3};
int currentItem = 0;
uint8_t item1Val = 10;

void menu1() 
{
   START_MENU(); 
       MENU_ITEM_EDIT(0, 3, 1, itemList, array, 6, "Item:", currentItem);
       MENU_ITEM_EDIT(0, 255, 1, uiToStr, func, 3, "Item 1 Value:", item1Val);
   END_MENU();
}

Что отображается как:

Item:     Item 1
Item 1 Value: 10     

Для удобства в объявлениях моих меню я пытаюсь использовать общие типы меню, такие как uint8_t, который находится в диапазоне от 0 до 255. Затем в меню я бы использовал __u255_t вместо первых 6 аргументов.

#define __u255_t 0, 255, 1, uiToStr, func, 3

void menu2()
{
    int item2Val = 10;

    START_MENU(); 
        MENU_ITEM_EDIT(__u255_t, "Item 2 Value", item2Val);
    END_MENU();
}

Ранее я настроил его таким образом, что каждый раз, когда мне требовался другой тип меню, даже если я использовал его только один раз, мне приходилось определять для него тип и то, как он должен оцениваться в макросе. Таким образом, я могу использовать 1 аргумент вместо 6 для часто повторяющихся типов в строках. Однако мне по-прежнему нужна возможность использовать параметр с 6 аргументами, когда у меня есть строка с уникальным набором аргументов, например другой диапазон или список отображаемых строк.

Однако в этом примере программа не будет компилироваться, потому что она помещает __u255_t в min в макросе MENU_ITEM_EDIT(), а не чем разложить __u255_t на 6 значений и поместить их в первые 6 аргументов MENU_ITEM_EDIT(). Макрос оценивается как:

MENU_ITEM_EDIT(__u255_t, "Item 2 Value", item2Val);
// Вычисляется как:
MENU_ITEM_EDIT( (0, 255, 1, uiToStr, func, 3), "Item 2 Value", item2val, , , , , );

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

Но я хочу:

MENU_ITEM_EDIT(__u255_t, "Item 2 Value", item2Val);
// Evaluates as:
MENU_ITEM_EDIT(0, 255, 1, uiToStr, func, 3, "Item 2 Value", item2Val);

Я думал об использовании struct, однако не уверен, что смогу, потому что в зависимости от строки строка value в draw_row() может быть функцией или массивом. Кто-нибудь может подсказать, как решить эту проблему?

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

, 👍1

Обсуждение

Я согласен - это вопрос языка, а не Arduino, но краткий ответ заключается в том, что макросы не являются частью языка (C или C++); они являются функцией препроцессора (конечно, стандартной для компиляторов C и C++), которая оценивается во время компиляции, поэтому они должны быть полностью доступны для оценки в это время. Следовательно, будучи оцененными до того, как компилятор увидит ваш код, они также не имеют возможности перегрузки, включая списки параметров переменной длины, которые предоставляет C++., @JRobert

включая списки параметров переменной длины -- на самом деле они это делают: __VA_ARGS__ и #define foo(...), @Majenko

Макросы прекомпилятора C/C++ — сплошной беспорядок. (Особенно макросы, которые принимают параметры, такие как те, которые вы используете.) Они не являются типобезопасными, и они разрешаются во время компиляции довольно запутанным образом. Есть и другие способы решения такого рода проблемы, @Duncan C


1 ответ


Лучший ответ:

1

Вам нужно превратить ваш макрос в макрос. Звучит странно, я знаю. Но возьмем следующий пример:

#define foo bar, baz
#define foobar(X, Y, Z) something(X, Y, Z);
#define boofar(...) foobar(__VA_ARGS__)

boofar(foo, 4)

boofar — это макрос, который принимает список переменных аргументов. Это расширяется в вызов вашего фактического вызова макроса, расширяя foo в процессе. Затем ваш фактический макрос расширяется с уже существующими реальными аргументами. Дело в том, что foo раскрывается перед передачей макросу, которому нужны несколько аргументов, находящихся внутри foo.

Итак, цепочка расширения на самом деле:

boofar(foo, 4) ->
foobar(bar, baz, 4) ->
something(bar, baz, 4);

Запустив его через cpp, вы получите:

# 1 "t.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "t.c"




something(bar, baz, 4);
,

Гак. Реквизит для объяснения того, как заставить его работать, но все равно гак., @Duncan C

Да, это сбивает с толку, но ваше объяснение имеет смысл. Спасибо!, @Antyos