c++constants

Initialize constant string array out of order


Trying to increase robustness of a piece of embedded code - simplified like this:

enum tags { 
  TAG1, 
  TAG2, 
  NUM_TAGS
}; 

const char* const vals_ok[] = { "val1", "val2" }; 

// Doesn't work (I understand why, but I look for ideas how to get it working again)
const char** const vals = []() constexpr {
  const char* rval[NUM_TAGS] = {0};
  rval[TAG2] = "val2";
  rval[TAG1] = "val1";
  return rval;
}();

I'm trying to ensure that the tags and values don't get out of sync with one another, if someone forgets to add a value in one but not the other place.

Since it's an embedded system, I want it to be a const-of-const array, reordered at compile-time - essentially I want the compiler to come up with the exact same output as in vals_ok - but with added maintainability.

It "works" with declaring the inner lambda variable rval static - but lands in .bss with a runtime copy initializer..


Solution

  • I'm trying to ensure that the tags and values don't get out of sync with one another, if someone forgets to add a value in one but not the other place.

    "Idiot-proofing" code like that would be tricky if the strings were defined in a different place than the tags. This is a good use of X macros, like:

    // All entries are managed here:
    
    #define TAG_ENUM_DATA(F) \
        F(TAG1, "val1") \
        F(TAG2, "val2")
    
    // Macro-expanded, do not touch:
    
    #define TAG_ENUM_ENUMERATOR(tag, str) tag,
    #define TAG_ENUM_STRING(tag, str) str,
    
    enum {
        // expands to: TAG1, TAG2,
        TAG_ENUM_DATA(TAG_ENUM_ENUMERATOR)
        NUM_TAGS
    };
    
    constexpr const char* vals_ok[] = {
        // expands to "val1", "val2",
        TAG_ENUM_DATA(TAG_ENUM_STRING)
    };
    
    constexpr const char* vals[] = {
        // expands to "val1", "val2",
        TAG_ENUM_DATA(TAG_ENUM_STRING)
        // one extra entry corresponding to NUM_TAGS
        nullptr
    };
    

    When someone wants to add a new entry or modify an existing one, they would just be modifying the TAG_ENUM_DATA macro; the enum and array are generated from that data.


    Note that modern, idiomatic C++ should probably use std::array instead of plain arrays, std::string_view instead of const char*, and enum class instead of enum. I assume you have good reasons to avoid these things, seeing that this is embedded.