Reputation: 1191
I might be overcomplicating this..
I'm trying to make a fairly reusable hierarchical menu system for an embedded app, in C on Arduino. I have structures to represent the different types of menu item, including those that are submenus, and a union of these to be a generic menu entry. An array of menu entries is one level of the menu. A submenu entry has a pointer to another array of menu entries.
Problem is, the submenu entry is a member of the union, so it needs to be defined before the union. But it has a pointer to an array of instances of this union, which causes a compile error because the union's not defined yet.
Is there a typesafe way of dealing with this, or does my pointer in the submenu entry have to be a void*?
Code:
typedef enum {UI_STATE_HOME, UI_STATE_MENU, UI_STATE_DIALOG} te_UIState;
typedef enum {UI_ENTRY_SUBMENU, UI_ENTRY_NUMERIC_INT, UI_ENTRY_NUMERIC_FLOAT, UI_ENTRY_BOOL, UI_ENTRY_DISCRETE} te_UIEntryType;
/* Typedefs for the different types of entry
*
*/
typedef struct {
char* entryName; // pointer to one of our string entries, eg MNU_Light
te_UIEntryType entryType;
} tsEntry;
typedef struct {
char* entryName;
te_UIEntryType entryType;
int (* finalIntHandler)(int selectedValue); //The function that should be called when a number has been selected
int (* initialIntValue) (); //The function that should be called to obtain the starting value for the selector
} ts_EntryInt;
typedef struct {
char* entryName;
te_UIEntryType entryType;
int (* finalIntHandler)(float selectedValue);
float (* initialSingleValue) ();
} ts_EntrySingle;
typedef struct {
char* entryName;
te_UIEntryType entryType;
int (* finalIntHandler)(bool selectedValue);
bool (* initialBoolValue) ();
} ts_EntryBool;
typedef struct {
char* entryName;
te_UIEntryType entryType;
int (* handler)();
char* (* optionalEntryNamePtrFunction)(); //If this points to a function, it'll be called to determine what text to display as the entry name.
// This is for things like enable/disable where the text changes depending on the present state.
} ts_EntryDiscrete;
typedef struct {
char* entryName;
const te_UIEntryType entryType = UI_ENTRY_SUBMENU;
void *entries[];
} ts_EntrySubmenu;
typedef union
{
tsEntry entry;
ts_EntryInt entryInt;
ts_EntrySingle entrySingle;
ts_EntryBool entryBool;
ts_EntryDiscrete entryDiscrete;
ts_EntrySubmenu entrySubmenu;
} tuEntry;
Upvotes: 3
Views: 61
Reputation: 180181
Problem is, the submenu entry is a member of the union, so it needs to be defined before the union.
Yes.
But it has a pointer to an array of instances of this union, which causes a compile error because the union's not defined yet.
The union does not need to have been defined for you to declare a pointer to that type. It only needs to have been declared. The definition can come later.
Is there a typesafe way of dealing with this, or does my pointer in the submenu entry have to be a void*?
Yes. Forward-declare the union before the structure definition, and put the union definition later. This does require you to provide a tag for your union type, else the forward declaration and later definition would not refer to the same type.
Example:
typedef union entry tuEntry;
typedef struct {
// ...
tuEntry *entries[];
} ts_EntrySubmenu;
union entry
{
// ...
ts_EntrySubmenu entrySubmenu;
};
Additionally, I cannot help noticing that your original code completely avoids declaring any structure or union tags. I want to make sure you understand that that's a style choice, and especially that the typedef
keyword is not about defining types, but rather about declaring aliases for type names. Your style is not inherently wrong, but it does not support what you want to do. You need to use structure and union types with tags to link multiple declarations of the same type in the same scope.
Upvotes: 1
Reputation: 171127
Declaring a pointer to some T
only requires that T
be declared, not necessarily defined. So just do this:
union Entry;
typedef struct {
char* entryName;
const te_UIEntryType entryType = UI_ENTRY_SUBMENU;
union Entry *entries[];
} ts_EntrySubmenu;
typedef union Entry {
/* as before */
} tsEntry;
Upvotes: 2
Reputation: 223739
You need to forward declare tuEntry
so that you can use it in ts_EntrySubmenu
. You'll need to give a tag name to that union so it can be referenced later.
Also, you can't initialize a field of a struct or union as a default when it is defined in C. You need to set that field in each relevant instance.
typedef union tuEntry tuEntry;
typedef struct {
char* entryName;
const te_UIEntryType entryType; // no default value
tuEntry *entries[];
} ts_EntrySubmenu;
union tuEntry
{
tsEntry entry;
ts_EntryInt entryInt;
ts_EntrySingle entrySingle;
ts_EntryBool entryBool;
ts_EntryDiscrete entryDiscrete;
ts_EntrySubmenu entrySubmenu;
};
Upvotes: 4