lerei
lerei

Reputation: 113

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?

Upvotes: 0

Views: 198

Answers (2)

Jarod42
Jarod42

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

cry0genic
cry0genic

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

Related Questions