Search code examples
c++wrapperstdint

Base and inherited objects to wrap stdint variables in C++


I am refactoring some C++ code for an AVR project that uses Sloeber (Arduino plugin for Eclipse). The project has many "settings" variables that are stored in EEPROM, have upper and lower limits, require string labels, etc. These settings are of different integer types (uint8_t, int32_t, etc), and I would like a wrapper that can contain any one of these types, with some methods inherited from a base class. I would also like to be able to form a single array of all the settings variables so I can iterate through them.

A simple implementation to demonstrate is as follows:

// Base class storing a uint8_t by default
class Base {
  public:
  typedef uint8_t numType;
  numType value = 0;  
};

// Child class changing 'numType' to a uint16_t
class Child: public Base {
  public:
  typedef uint16_t numType;
};

Then run the following:

Base baseObj;
baseObj.value = 123;

Child childObj;
childObj.value = 12345;

My intention was that childObj.value would be a uint16_t, whilst baseObj.value would remain a uint8_t.

However childObj.value evaluates to 57, so it is still being treated as a uint8_t. Any thoughts on a way to achieve this sort of thing?


Solution

  • What you want is a form of type erasure. You can either use std::any directly (this would make sense if you only stored a value) or build your own:

    class Setting {
     public:
      Setting(std::string description) : _description(description) {}
      virtual ~Setting() = 0;
    
      std::string getDescription();
    
      template<typename T>
      T getValue();
    
     private:
      std::string _description;
    };
    
    template <typename T>
    class SettingTyped : public Setting {
     public:
      SettingTyped(T value, std::string description)
          : Setting(description), _value(value) {}
    
      T getValue() { return _value; }
    
     private:
      T _value;
    };
    
    Setting::~Setting() = default;
    
    template<typename T>
    T Setting::getValue()
    {
        auto* typedSetting = dynamic_cast<SettingTyped<T>*>(this);
        if (!typedSetting)
            throw std::runtime_error("Accessing with wrong type!");
        return typedSetting->getValue();
    }
    
    template<typename T>
    auto makeSetting(T value, std::string description)
    {
        return std::make_unique<SettingTyped<T>>(value, description);
    }
    
    bool foo() {
      std::vector<std::unique_ptr<Setting>> settings;
      settings.push_back(makeSetting<int>(3, "a setting"));
    
      return (settings[0]->getValue<int>() == 3);
    }
    

    Demo

    You can play with this to figure out how to differentiate between "prototype setting" (default values, bounds) and "current setting value" (the actual stored value). For example, you can still decide whether the bounds should be encoded in the settings type (and you create a separate type for each kind of setting) or whether the bounds are (potentially) different constants every instance. The requirements for this are not clear from your question.

    It is particularly unclear how you expect to know the correct type for each setting when you iterate through them. It is assumed here that you somehow know (e.g. based on the description?).