Typesafe operator[] for map<string, variant>
-
Hi all,
I'm currently working on a simple config data storage (typesafe), but I've some problems to implement the accessors.
class ConfigData { using dataTypes = variant<bool, uint8_t, uint16_t, string>; public: ConfigData(string _name, const map<string, dataTypes>_data); uint8_t& operator[](const string& _key) { return std::get<uint8_t>(Data[_key]); } template<typename T> T operator[](const string& _key) const { return std::get<T>(Data[_key]); } private: map<string, dataTypes> Data; }; ConfigData config( "test", { {"foo", "bar"}, {"myInt", (uint8_t)2} } );
What is currently working (but in some sort uggly):
uint8_t a = config.operator[]<uint8_t>("myInt"); config.operator[]<uint8_t>("myInt") = 5; uint8_t b = config.operator[]<uint8_t>("myInt");
What I want to acheave:
uint8_t a = config["myInt"]; config["myInt"] = 5; uint8_t b = config["myInt"];
What is known:
- The number of config elements is known at compile time and not changed
- All types are known at compile time
- No type changes at runtime
- only one type per config-element
Can someone help me, or show the direction?
Best regards
P51D
-
You do speak German, do you not? At least your posting history says so.
Anyways: This is not possible. The fact that you want to return an
uint8_t
from your map must come from somewhere. But the other side of your assignments cannot be used to deduce the template parameters for your calls. The reverse is true: You must somehow provide those types, so that the compiler can check if your assignments are valid after calling your getters.Depending on the context, you can build something decent looking by using
std::visit
, but you probably already know that, and it does not really help you in this particular case.
-
Es funktioniert, wenn man den Zugriff auf die
map
innerhalb desconst
-Operators ändert ([]
funktioniert nur bei nicht-konstanten Objekten bzw. innerhalb von nicht-konstanten Memberfunktionen *):template<typename T> T operator[](const string& _key) const { return std::get<T>(Data.at(_key)); }
s.a. Compiler Explorer Code (für MSVC 19.latest und gcc 14.2)
* Deshalb kann
config.operator[]<uint8_t>("myInt") = 5;
eigentlich nicht bei dir kompilieren (habe ich daher aufconfig.operator[]("myInt") = 5;
[bzw. entsprichtconfig["myInt"] = 5;
] geändert), da dertemplate
-Operator keine Referenz (L-Value) zurückgibt.Trotzdem sehe ich nicht, was daran 'typesafe' sein soll. Der Auslesecode muß ja trotzdem immer den eingetragenen Datentyp kennen (außer jetzt bei der Überladung mittels
uint8_t
).
Wäre eine generelle Typkonvertierung nicht sinnvoller?
-
Also mich überzeugt das nicht denn
#include <map> #include <cstdint> #include <variant> #include <string> #include <cstdlib> #include <iostream> using std::variant; using std::string; using std::map; class ConfigData { using dataTypes = variant<bool, uint8_t, uint16_t, string>; std::string Name; std::map<std::string, dataTypes> Data; public: ConfigData(string _name, const map<string, dataTypes>_data) : Name(_name), Data(_data) {} template<typename T> T operator[](const string& _key) { auto it = Data.find(_key); if (it == Data.end()) { Data.insert(T{}); } return std::get<T>(Data.at(_key)); } }; int main() { ConfigData config( "test", { {"foo", "bar"}, {"myInt", uint8_t{2}} } ); // std::cout << config["foo"] << "\n"; // std::cout << config["myInt"] << std::endl; // auto str = config["foo"]; return EXIT_SUCCESS; }
Das lässt sich nur übersetzen, wenn man die Zeilen auskommentiert lässt. Das Problem bleibt, dass ConfigData::operator[] einen der Typen aus der Variant zurückliefert, und der Compiler dies nicht über den Parameter des operator[] auflösen kann. Sinnvoller wäre es meines Erachtens in die Map einen Polymorphen Wrapper abzulegen, den man dann per dynamic_cast in den gewünschten Typen verwandeln kann, bzw. bei dem es entsprechende Member Functions gibt. C++ ist halt keine Sprache, die solche Dinge erlauben würde.