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..
As suggested in comments, std::array should make this easier to write:
constexpr std::array vals = []()
{
std::array<const char*, NUM_TAGS> rval{};
rval[TAG2] = "val2";
rval[TAG1] = "val1";
return rval;
}();
On godbolt
I'm not sure if this really ensures that your values and names don't get out of sync, but you can add a static_assert that you didn't miss any, like static_assert(!std::ranges::contains(vals, nullptr)); (example on godbolt; example when you forgot a tag).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With