1

EDIT: I think this fits better in the C++ Stack Overflow, so I'm going to re-post this there.

I am working on a menu system with #define macros (inspired by the Marlin firmware on my 3D printer) I am attempting to generalize each menu item with the macro #define MENU_ITEM_EDIT(). The full code is really long, so I am leaving out chunks of code of compactness. However, you can find the full code here: https://pastebin.com/h24itG9y

void draw_row(int row, char* value) { /* Stuff here*/ } // Draws row
char* uiToStr(int x) { /* Stuff here*/ } // Converts unsigned int to a string

#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) \
    // A bunch of stuff
    draw_row(rowNum, disp_ ## displayType(display, value)); \
    // More stuff

Using the code macro would look like:

// Values
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();
}

Which displays as:

Item:     Item 1
Item 1 Value: 10     

For convenience in the declarations of my menus, I am trying to use common menu types, such as an uint8_t that ranges from 0 to 255. Then, in the menu, I would use __u255_t instead of the first 6 arguments.

#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();
}

Previously, I had it set up such that every time I wanted a different type of menu, even if I would only use it once, I had to define a type for it and how it should evaluate in the macro. This way, I can use 1 argument instead of 6 for commonly recurring types in rows. However, I still want the ability to use the 6 argument option when I have a row that has a unique set of arguments, such as a different range or list of display strings.

However, in this example, the program won't compile because it places __u255_t into min in the MENU_ITEM_EDIT() macro, rather than expanding __u255_t into its 6 values and placing them into the first 6 arguments of MENU_ITEM_EDIT(). The macro evaluates as:

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

Note: I put an extra set of parentheses to make it more clear to understand.

But I want:

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

I thought about using a struct, however I'm not sure that I can because depending on the row, the value string in draw_row() could be a function or an array. Does anyone have any advice on how to solve this problem?

Also, please let me know if I need to add any more code to make my problem more clear.

  • I agree - this is a language question, not an Arduino one - but the short answer is that macros are not part of the language (C or C++); they are a preprocessor feature (standard on C and C++ compilers, to be sure) that is evaluated at compile time, so they must be completely evaluable at that time. Therefore, being evaluated before the compiler ever sees your code, they also don't have the overloading capability, including variable-length parameter lists, that C++ provides. – JRobert Jul 3 at 21:47
  • including variable-length parameter lists -- actually they do: __VA_ARGS__ and #define foo(...) – Majenko Jul 3 at 22:14
  • C/C++ precompiler macros are a hot mess. (Especially macros that take parameters, like the ones you're using.) They're not type-safe, and they get resolved at compile-time in ways that are quite confusing. There are other ways of solving this sort of problem – Duncan C Jul 3 at 22:52
1

You need to turn your macro itself into a macro. Sounds odd, I know. But take the following example:

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

boofar(foo, 4)

boofar is a macro that takes a variable argument list. That expands out into a call for your actual macro call, expanding foo in the process. Then your actual macro gets expanded out with the real arguments already in here. The point here is that foo is expanded before being passed to the macro that needs the multiple arguments that are inside foo.

So the expansion chain is actually:

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

Running it through cpp and you get:

# 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);
  • Gak. Props for explaining how to make it work, but gak just the same. – Duncan C Jul 3 at 22:53
  • Yeah, it is confusing, but your explanation makes sense. Thanks! – Antyos Jul 8 at 18:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.