'Syntax for declaring a pointer to an array of struct as a parameter

I am building an Arduino project in C++. I have an OLED display that I want to use to present a number of menus to the user. The behaviour of these menus will be the same but the number and detail of the options is variable. So I have created a base class (RotaryMenu) that will define the common methods and then a number of child classes that will define their specific options.

The base class methods will need to access the detail of the options, so I am calling the base class constructor from the child class with a pointer to an array containing the data that is specific to the child class.

I think that this is the correct approach for an embedded application where memory is valuable.

I am struggling to the correct syntax for passing the pointer to the array. I have tried various things but the C++ compiler seems determined to thwart my intentions

Here is the code;

struct MenuOption {
  char text[21];
  unsigned value;
 };

//Menu base class - not directly useable
//You must creat a child class that populates
//the menu optionList etc.

class RotaryMenu {
  private:
    MenuOption optionList[];
    char* title;
    size_t numOptions;
  public:
    //Constructor populates optionList
    RotaryMenu( char * _title, MenuOption *options, size_t num ) {
      optionList = options;
      title = _title;
      numOptions = num;
    }
  protected:
    void show(unsigned first);
};

void RotaryMenu::show(unsigned first=0) {

//Code deleted for clarity  

    for (int i=first; i<numOptions && i<6; i++) {
      display.println(optionList[i]->text); //Compiler doesn't like this
    }
 }


//Example child class
//Calibration menu -
class CalibrationMenu : public virtual RotaryMenu {

  public:
    CalibrationMenu() : RotaryMenu( title, optionList, numOptions );
    const char* title {"Calibration Menu"};
    const size_t numOptions {4};
    const MenuOption optionList[4] = {
      {"Boat compass hdg.",1},
      {"Reload saved offsets",2},
      {"Manual calibration",3},
      {"Cancel and return",0}
    };
 } calibrationMenu;




Solution 1:[1]

Change the optionList member variable to be of type MenuOption const* instead of an invalid empty array, adjust the constness of your other various parameters and members, and add a function body to the CalibrationMenu constructor definition.

Then worry about construction order, because base classes are constructed before member variables.

Solution 2:[2]

const MenuOption optionList[4] ...

This is an array of const objects.

RotaryMenu( title, optionList, numOptions );

When it gets passed as a parameter, the array decays to a const MenuOption *, accordingly, a pointer to const objects.

RotaryMenu( char * _title, MenuOption *options, size_t num ) {

But the parameter is declared as a MenuOption *, instead of a const MenuOption *. C++ does not allow a pointer to const objects to get converted to a pointer to non-const objects. This has nothing to do with arrays. The same thing will happen if plain pointers were used from the beginning.

MenuOption optionList[];

This class member declaration is also confusing, if not plain wrong. This should also be a pointer, and for the same reasons as above this should be a const MenuOption *optionList.

Note that this entire arrangement results in a base class having a pointer to a member of its (virtual) sub-class. This is not completely wrong, per se, but will result in surprising behavior if these objects get copied or moved.

If your intent was not that, but for the subclass to actually own and contain an array of its own, then the whole thing needs to be done in a completely different manner.

Solution 3:[3]

I think I have now cracked it using a slightly different approach. See code below, this seems to work. I have now declared the data fields as protected members of the base class and I am initialising them in the subclass constructor. This avoids the need to call the base-class constructor from the child and hence avoids worrying about passing the fields as parameters. As Stephen has suggested, I did have to remove all arrays of undefined length. I have just set a maximum length (8) which is bigger than I will ever need. I did think about the possibility of using std:::vector, or LinkedLists but think that this would be overkill. Remember this is an embedded app running on a microcontroller with limited memory, I'm trying to keep the libraries etc. used to a minimum. I also had to alter the bracket list assignment to assign each of the array elements individually. These menus are global singletons that will be created just once.

Many thanks for your help. Please let me know If you think that the above approach has issues.

struct MenuOption {
  char *text;
  unsigned value;
 };

//Menu base class - not directly useable
//You must create a child class that populates
//the menu optionList etc.

class RotaryMenu {

  public:
    void show(unsigned first);
  
  protected:
    MenuOption optionList[8];
    char* title;
    size_t numOptions;
};

void RotaryMenu::show(unsigned first=0) {

    for (int i=first; i<numOptions && i<6; i++) {
      Serial.println(optionList[i].text);
    }
 }


 //Calibration menu
class CalibrationMenu : public virtual RotaryMenu {
  public:
    CalibrationMenu()  {
      title = "Calibration Menu";
      numOptions = 4;
      optionList[0] = {"Boat compass hdg.",1};
      optionList[1] = {"Reload saved offsets",2};
      optionList[2] = {"Manual calibration",3};
      optionList[3] = {"Cancel and return",0};     
    }
    void debug() {
      for( int i=0; i<numOptions; i++ ) {
        Serial.println(optionList[i].text);
      }
    }
 } calibrationMenu;



void setup() {

Serial.begin(115200);
 
 while (!Serial)
    ;
}

void loop() {
    calibrationMenu.show();
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Stephen M. Webb
Solution 2 Sam Varshavchik
Solution 3 Rob W