1

I'm trying to write a relatively simple Settings list structure, but after three different failed design iterations, I'm sure I'm missing a keyword to find the name of my exact problem.

Ideally, I'd like:

  1. Each setting has a unique Key, for easy de(serialisation)
  2. Each setting in the list is a class member, or has a named accessor fn, so my IDE can auto-complete
  3. Each setting can be a different type (String, uint8_t, uint_16_t...)
  4. Each setting has a const char* name
  5. The list has a compile-time known size and a random-access-iterator, so I can use array subscripting

Attempt 1

#define MAKE_UNION(struct_name, struct_def)                     \
  struct __dummy_for_size__##struct_name struct_def;            \
                                                                \
  struct struct_name                                            \
  {                                                             \
    union                                                       \
    {                                                           \
      struct struct_def;                                        \
      uint8_t as_list[sizeof(__dummy_for_size__##struct_name)]; \
    };                                                          \
  };

MAKE_UNION(InputSettings, {
  uint8_t source;
  uint8_t channel;
  bool active;
}) input_settings;

Pros:

  1. When I type input_settings.s, IDE can autocomplete to input_settings.source
  2. Easy array subscripting with input_settings.as_list[?]

Cons:

  1. Code feels icky, I might be in undefined behaviour land, with the union. I was even casting to void* before that...
  2. I'm limited to types of one byte, otherwise my as_list subscripting falls apart
  3. No unique key, (de)serialisation is difficult to make backward/forward compatible
  4. Settings don't have a c-str name

Attempt 2

Most of the implementation is omitted for brevity

struct ISetting{
    const std::string unique_key;
    /*...*/
}; 
class SettingList
{
    std::vector<std::unique_ptr<ISetting>> list;
};

class ValueSetting : public ISetting {/*...*/};
class TextValueSetting : public ISetting{/*...*/};



SettingList input_settings;
input_settings.list.push_back(TextValueSetting{"SOURCE", {"A", "B"}, 0});
input_settings.list.push_back(ValueSetting{"CHANNEL", 0, 15, 0});
input_settings.list.push_back(ValueSetting{"ACTIVE", 0, 1, 1});

auto get_source_setting = [&]{
    return input_settings.list[0];
};

Pros:

  1. Unique keys make it easy to de(serialise)
  2. Settings have names

Cons:

  1. Too complicated
  2. Probably unnecessary runtime polymorphism. Plus because IRL I'm not using RTTI, I have to bake in the type with an enum and add that to the interface in order to downcast.
  3. Uses a vector but the size is known
  4. Settings don't have named accessors. They have to be written and maintained manually (e.g. the get_source_setting lambda).

Attempt 3

struct Value
{
    uint16_t val;
};
struct TextValue
{
    uint16_t val;
    std::span<std::string_view> values_names;
}; 

std::array<std::pair<std::string, std::variant<Value, TextValue>>, 3> input_settings
{{
    {"SOURCE",  TextValue{ /*... */ }},
    {"CHANNEL", Value{ /*... */ }},
    {"ACTIVE",  Value{ /*... */ }},
    //...
    //...
}};

Pro:

  1. Unique key
  2. Random access
  3. Could abstract away into a class for easy get_by_key(std::string)

Cons:

  1. No named accessor
  2. Non random access iterator
  3. Wasted memory because of the memory layout of std::variant (minor inconvenience)

Even a get_by_key(std::string) would imply a runtime performance cost. Maybe a compile time get<key_t>() is possible ?

TL;DR:

How can I mix and match different types into a container, and have each element have its own dedicated named getter/setter automatically ? All the elements are known at compile time, I'd like to avoid runtime overhead.

Nebular Noise
  • 388
  • 3
  • 15
  • Unrelated to your problem, but please note that all symbols starting with double underscore are reserved, and should not be declared or defined by your code. See [What are the rules about using an underscore in a C++ identifier?](https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) – Some programmer dude Jul 22 '22 at 14:51
  • 1
    You might be in danger of trying to micro-optimise here. Also, why do you need `operator[]`? I've never needed that in my settings class. – Paul Sanders Jul 22 '22 at 15:18
  • I think perhaps you are asking how to do *reflection* in C++, in which case the question [How can I add reflection to a C++ application?](https://stackoverflow.com/questions/41453/how-can-i-add-reflection-to-a-c-application) might help. – Scott McPeak Jul 23 '22 at 13:21
  • 1
    @PaulSanders I think you're right, the `operator[]` is a previous bad decision which I didn't take time to reconsider. @ScottMcPeak Thanks for mentioning reflection, I found boost::pfr which seems to provide a good workflow for my use case. – Nebular Noise Jul 25 '22 at 15:02

0 Answers0