Reputation: 113
I have class that is managing a number of similar member variables of different, but similar type.
class Manager {
Type1 a;
Type2 b;
Type3 c;
void process();
};
Although the types are different, they share some functions and attributes. At least this lets me use some templated functions to process the class members in a similar way.
template <typename T>
void process(T& t) {
// do something with t
}
void Manager::process() {
process(a);
process(b);
process(c);
}
At one point in my program I also need to supply strings equal to the member variable names.
template <typename T>
void print(T t, std::string name) {
std::cout << name << ": " << t.msg << std::endl;
}
void Manager::print() {
print(a, "a");
print(b, "b");
print(c, "c");
}
I have reduced the code samples to illustrate my problem. In reality, there are many more places where I simply copy-paste entire blocks of code for each variable. Every now and then, a new member variable with a new type is added.
What pattern can I use to avoid these seemingly unnecessary repetitions of similar code?
I have thought of something like a std::map<std::string, ParentType> members;
. I guess this would allow me to loop over all member variables instead of copy-pasting code and I could also store a corresponding name string for each variable. However, the types that I use have no common parent class and are third-party, i.e. I cannot modify the implementation of Type1
/Type2
/Type3
/...
I guess what I really want is to have only a single place where I define types and names and then being able to simply loop over all variables for performing similar processing.
I can think of partially solving this using preprocessor macros, but aren't those rather discouraged in modern code?
Upvotes: 0
Views: 198
Reputation: 217075
You might create template function for your class, something like:
class Manager {
Type1 a;
Type2 b;
Type3 c;
template <typename F>
void apply(F f)
{
f(a); f(b); f(c);
}
template <typename F>
void apply_with_name(F f)
{
f("a", a); f("b", b); f("c", c);
}
void process() { apply([](auto& t){ t.process(); }); }
void print() { apply_with_name([](const std::string& name, auto& t){ std::cout << name << ": " << t.msg << std::endl; }); }
};
Upvotes: 1
Reputation: 594
It seems like this is exactly the use case for the preprocessor---removing repeating code and stringifying identifiers. If you don't like it, and you don't mind the horrible syntax, you could make Manager
a tuple:
class Manager {
std::tuple<Type1, Type2, Type3 /* more types can be added ... */> tup;
}
Then you can run a function on each element using std::apply
. See Template tuple - calling a function on each element.
void Manager::process() {
std::apply([](auto ...x){
std::make_tuple(process(x)...);
}, this->tup);
}
Without reflection I believe there is no cleaner solution. And your stringify print example is impossible without macros.
Upvotes: 1