Search code examples
c++design-patternspolymorphism

C++ How to avoid repetitive code for variables of different third-party type?


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?


Solution

  • 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.