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.
You can avoid the issue of copying allElement
by using member function pointers rather than bare pointers. The map does not need to be a member of the class at all though.
struct Config {
std::string paramThis;
std::string paramOther;
std::string paramMore;
// man many more parameters.
};
const std::string& get_member(const std::string& name,const Config& config) {
static const std::unordered_map<std::string, std::string Config::*> mapping {
{"this",&Config::paramThis},
{"other",&Config::paramOther},
{"more",&Config::paramMore}
};
auto it = mapping.find(name);
if (it != mapping.end()) return (config.*(it->second));
throw "invalid name";
}
This requires only minimal changes on your main to get expected output. Live Demo
Using std::unordered_map
in place of your std::vector
+ std::find_if
is not essential, it's just easier to write. Also turning the function into a free function is not essential, it can be a member. The important change is too look up the members not via pointers to specific object, but via member pointers. That map (mapping
) could also be copied without loosing the ability to look up members of another instance.
If you want to have custom copy or move semantics you can supply your custom copy / move constructors. Though I advise to do so only when necessary. It is not necessary here.
If you still want it you would have to update the pointers in allElements
to point to the right members on a copy or move. Not copying it would not be sufficient. Though repopulating the map (or vector) on every copy / move is rather expensive (compared to not doing anything extra).