I'm using a single structure to store multiple parameters. I want to access these parameters via their element names, just like a normal structure. I also want to name the structure elements and access them using a string.
I came up with the following solution:
#include <string> #include <vector> #include <iostream> #include <stdexcept> #include <algorithm> struct Config { std::string paramThis; std::string paramOther; std::string paramMore; // man many more parameters. struct Element { const char *name; std::string *stringRef; }; const std::vector<Element> allElements = { {"this", ¶mThis}, {"other", ¶mOther}, {"more", ¶mMore} }; const Element & findElem(const std::string &name) const { auto eIter = std::find_if( allElements.begin(), allElements.end(), [&name](const auto &elem) { return (name == elem.name); }); if (eIter == allElements.end()) { throw std::invalid_argument("unknown member element"); } return *eIter; } }; int main() { auto printByElem = [](const auto &elem) { std::cout << "by element: " << elem << " (" << (void *)&elem << ")\n"; }; auto printByName = [] (const auto &config, const auto &name) { auto stringRef = config.findElem(name).stringRef; std::cout << "by name: " << name << '=' << *stringRef << " (" << (void *)stringRef << ")\n"; }; Config testConf1 { .paramThis = "this data", .paramOther = "other data", .paramMore = "more data" }; std::cout << "ORIGINAL\n"; printByElem(testConf1.paramThis); printByElem(testConf1.paramOther); printByElem(testConf1.paramMore); printByName(testConf1, "this"); printByName(testConf1, "other"); printByName(testConf1, "more"); std::cout << '\n'; Config testConf2 = testConf1; std::cout << "COPY\n"; printByElem(testConf2.paramThis); printByElem(testConf2.paramOther); printByElem(testConf2.paramMore); printByName(testConf2, "this"); printByName(testConf2, "other"); printByName(testConf2, "more"); std::cout << '\n'; Config testConf3 = std::move(testConf1); std::cout << "MOVE\n"; printByElem(testConf3.paramThis); printByElem(testConf3.paramOther); printByElem(testConf3.paramMore); printByName(testConf3, "this"); printByName(testConf3, "other"); printByName(testConf3, "more"); std::cout << '\n'; return 0; } Works fine until i copy or move the struct:
ORIGINAL by element: this data (0x7ffe585ba700) by element: other data (0x7ffe585ba720) by element: more data (0x7ffe585ba740) by name: this=this data (0x7ffe585ba700) by name: other=other data (0x7ffe585ba720) by name: more=more data (0x7ffe585ba740) COPY by element: this data (0x7ffe585ba780) by element: other data (0x7ffe585ba7a0) by element: more data (0x7ffe585ba7c0) by name: this=this data (0x7ffe585ba700) by name: other=other data (0x7ffe585ba720) by name: more=more data (0x7ffe585ba740) MOVE by element: this data (0x7ffe585ba800) by element: other data (0x7ffe585ba820) by element: more data (0x7ffe585ba840) by name: this= (0x7ffe585ba700) by name: other= (0x7ffe585ba720) by name: more= (0x7ffe585ba740) The problem is the vector, with the pointers, which is also copied or moved.
I could of course write constuctors that copy or move the elements individually. But since I have a lot of parameters, I want to avoid that. It would also be very error-prone when making changes. A new parameter would have to be handled in too many places.
Another option would be to put the parameters in a separate substructure. However, this would not be as attractive in terms of use. For example, I would simply like to use the configuration as a parameter like this: someMethod({.paramThis = “Test”});. A substructure would interfere with this type of use.
Does anyone have an idea whether it is possible to prevent the copying or moving of a single element in a struct via the default constructors / default assignments? Or has another idea how to solve this elegantly?
Many thanks to anyone who tried to help. My solution is now this:
#include <string> #include <iostream> #include <stdexcept> #include <algorithm> struct Config { std::string paramThis; std::string paramOther; std::string paramMore; // man many more parameters. struct Element { const char *name; std::string Config::*stringRef; }; const Element & findElem(const std::string &name) const; }; static const Config::Element allElements[] = { {"this", &Config::paramThis}, {"other", &Config::paramOther}, {"more", &Config::paramMore} }; inline const Config::Element & Config::findElem(const std::string &name) const { auto eIter = std::find_if( std::begin(allElements), std::end(allElements), [&name](const auto &elem) { return (name == elem.name); }); if (eIter == std::end(allElements)) { throw std::invalid_argument("unknown member element"); } return *eIter; } int main() { auto printByElem = [](const auto &elem) { std::cout << "by element: " << elem << " (" << (void *)&elem << ")\n"; }; auto printByName = [] (const auto &config, const auto &name) { auto stringRef = config.findElem(name).stringRef; std::cout << "by name: " << name << '=' << config.*stringRef << " (" << (void *)&(config.*stringRef) << ")\n"; }; Config testConf1 { .paramThis = "this data", .paramOther = "other data", .paramMore = "more data" }; std::cout << "ORIGINAL\n"; printByElem(testConf1.paramThis); printByElem(testConf1.paramOther); printByElem(testConf1.paramMore); printByName(testConf1, "this"); printByName(testConf1, "other"); printByName(testConf1, "more"); std::cout << '\n'; Config testConf2 = testConf1; std::cout << "COPY\n"; printByElem(testConf2.paramThis); printByElem(testConf2.paramOther); printByElem(testConf2.paramMore); printByName(testConf2, "this"); printByName(testConf2, "other"); printByName(testConf2, "more"); std::cout << '\n'; Config testConf3 = std::move(testConf1); std::cout << "MOVE\n"; printByElem(testConf3.paramThis); printByElem(testConf3.paramOther); printByElem(testConf3.paramMore); printByName(testConf3, "this"); printByName(testConf3, "other"); printByName(testConf3, "more"); std::cout << '\n'; return 0; } I use an array instead of a map to create a compile-time structure.
namenot astd::string?std::unordered_map<std::string,std::string>const char *does not need a constructor and does not allocate extra heap memory. Since i have to usec++14, i also could not usestd::string_view.allElementsas function instead of variable member, then it won't suffer from copy. Caveat is that you would need to store it locally to be able to usebegin()/end()on same container.std::array,std::initializer_listdoesn't own its data. Demo