A slightly more organized approach to the giant switch statement is pretty common. If you have a class, implementing it as a bag of random Value objects is pretty inefficient, difficult to use safely, can't be verified or analyzed properly, and is just all-around a bad idea. Use a bag of values when you need heavy dynamism, not just to make a UI editor for an existing type. There are plenty of articles, blog posts, and forum comments about how to add reflection/introspection to C++ to make it easier to build editor UIs automatically out of marked-up classes.
If you really do need the dynamism, one option is to have a separate data structure for each type. e.g., instead of:
class FloatValue : IValue;
class IntValue : IValue;
etc.
std::map<std::string, std::unique_ptr<IValue>> values;
use something like this instead:
class NaivePropertyBag final {
std::map<std::string, float> _floatValues;
std::map<std::string, int> _intValues;
public:
void SetInt(std::string name, int value) {
_floatValues.erase(name);
_intValues[move(name)] = value;
}
bool GetInt(std::string const& name, int& out_value) const {
auto iter = _intValues.find(name);
if (iter == _intValues.end()) {
return false;
} else {
out_value =
return iter == _intValues.end() ? 0 : iter->second;
}
// same for float, etc.
};
Note that that's just a general approach, not code I would in any way recommend using as a model. You can also continue using IntValue, FloatValue, etc., but just wrap the data structure using them into a safe container that does any casting or checks internally rather than exposing those to the user.
In place of the one you have, you can use a visitor pattern or a type-aware value interface, something like:
class ValueBase {
public:
virtual ~ValueBase() = default;
virtual bool IsInt() const { return false; }
virtual bool IsFloat() const { return false; }
virtual int GetInt() const { return 0; }
virtual float GetFloat() const { return 0.f; }
};
class FloatValue final : public ValueBase {
float _value;
public:
bool IsFloat() const override { return true; }
float GetFloat() const override { return _value; }
};
class PropertyBag final {
std::map<std::string, std::unique_ptr<ValueBase>> _values;
public:
void SetInt(std::string name, int value) {
_values[std::move(name)] = std::make_unique<IntValue>(value);
}
void SetFloat(std::string name, float value) {
_values[std::move(name)] = std::make_unique<FloatValue>(value);
}
bool GetFloat(std::string const& name) {
auto iter = _values.find(name);
if (iter == _values.end())
return false;
if (!iter->second->IsFloat())
return false;
return iter->second->GetFloat();
}
};
And you can simplify that further with an enumations, allow type conversions where appropriate, etc.