carsten
carsten

Reputation: 1365

use c++ preprocessor to autogenerate repetitive code

I'm currently dealing with a piece of extremely repetitive code that is implementing and instantiating a series of template constructors. The general pattern is somewhat like this:

// preamble: some dummy classes to work with
class A{};
class B{};
class C{};
class D{};
class E{};
class F{};
class G{};

class X{};
class Y{};

// the actual class declaration, located in some header file

class MyClass {
  A* _a;
  B* _b;
  C* _c;
  D* _d;
  E* _e;
  F* _f;
  G* _g;

 public:
  template<class T> void foo(const std::vector<T>& args){};

  MyClass(A* a = NULL, B* b = NULL, C* c = NULL, D* d = NULL, E* e = NULL, F* f = NULL, G* g = NULL);
  template<class T> MyClass(A* a, B* b, C* c, D* d, E* e, F* f, G* g, std::vector<T> args1);
  template<class T> MyClass(A* a, B* b, C* c, D* d, E* e, F* f, std::vector<T> args1);
  template<class T> MyClass(A* a, B* b, C* c, D* d, E* e, std::vector<T> args1);
  template<class T> MyClass(A* a, B* b, C* c, D* d, std::vector<T> args1);
  template<class T> MyClass(A* a, B* b, C* c, std::vector<T> args1);
  template<class T> MyClass(A* a, B* b, std::vector<T> args1);
  template<class T> MyClass(A* a, std::vector<T> args1);
  template<class T> MyClass(std::vector<T> args1);
};

// default constructor, this is still fine 

MyClass::MyClass(A* a, B* b, C* c, D* d, E* e, F* f, G* g) : _a(a),_b(b),_c(c),_d(c),_e(e),_f(f),_g(g) {};

// here the horribly repetitive part begins
// there are lots of repetitions of this block, every time with a slightly different signature

template<class T> MyClass(A* a, B* b, C* c, D* d, E* e, F* f, G* g, std::vector<T> args1) : MyClass(a,b,c,d,e,f,g) {
  foo<T>(args);
}
template MyClass<X>(A*, B*, C*, D*, E*, F*, G*, std::vector<X> args1);
template MyClass<Y>(A*, B*, C*, D*, E*, F*, G*, std::vector<Y> args1);

template<class T> MyClass(A* a, B* b, C* c, D* d, E* e, F* f, std::vector<T> args1) : MyClass(a,b,c,d,e,f) {
  foo<T>(args);
}
template MyClass<X>(A*, B*, C*, D*, E*, F*, std::vector<X> args1);
template MyClass<Y>(A*, B*, C*, D*, E*, F*, std::vector<Y> args1);

template<class T> MyClass(A* a, B* b, C* c, D* d, E* e, std::vector<T> args1) : MyClass(a,b,c,d,e) {
  foo<T>(args);
}
template MyClass<X>(A*, B*, C*, D*, E*, std::vector<X> args1);
template MyClass<Y>(A*, B*, C*, D*, E*, std::vector<Y> args1);

template<class T> MyClass(A* a, B* b, C* c, D* d, std::vector<T> args1) : MyClass(a,b,c,d) {
  foo<T>(args);
}
template MyClass<X>(A*, B*, C*, D*, std::vector<X> args1);
template MyClass<Y>(A*, B*, C*, D*, std::vector<Y> args1);

template<class T> MyClass(A* a, B* b, C* c, std::vector<T> args1) : MyClass(a,b,c) {
  foo<T>(args);
}
template MyClass<X>(A*, B*, C*, std::vector<X> args1);
template MyClass<Y>(A*, B*, C*, std::vector<Y> args1);

template<class T> MyClass(A* a, B* b, std::vector<T> args1) : MyClass(a,b) {
  foo<T>(args);
}
template MyClass<X>(A*, B*, std::vector<X> args1);
template MyClass<Y>(A*, B*, std::vector<Y> args1);

template<class T> MyClass(A* a, std::vector<T> args1) : MyClass(a) {
  foo<T>(args);
}
template MyClass<X>(A*, std::vector<X> args1);
template MyClass<Y>(A*, std::vector<Y> args1);

template<class T> MyClass(A* a, std::vector<T> args1) : MyClass() {
  foo<T>(args);
}
template MyClass<X>(std::vector<X> args1);
template MyClass<Y>(std::vector<Y> args1);

// here some main function just to make it compile    

int main(){
  std::vector<X> x;
  MyClass c(NULL,NULL,NULL,x);
  return 1;
}

As you can see, there is a specific type of block that is repeated over and over again

template<class T> MyClass(ARG* arg, ..., const std::vector<T> &args) : Myclass(arg,...) { foo<T>(args); } 
template MyClass<X>(ARG* arg, ..., const std::vector<X>& args)
template MyClass<Y>(ARG* arg, ..., const std::vector<Y>& args)

This code is, of course, a nightmare to maintain, and I find myself repeatedly using sed to edit the code, which is of course bad practice and bound to fail at some point.

I think the repetition could be abstracted away using some preprocessor macro

#define GENERATE_CONSTRUCTOR(...) ???

GENERATE_CONSTRUCTOR(A,a,B,b,C,c,D,d)

I would imagine that it would be possible to write GENERATE_CONSTRUCTOR as some variadic preprocessor macro with a FOREACH loop, but unfortunately, all the documentation I could find on the topic of variadic preprocessor macros was highly confusing to me, and not terribly helpful since most examples are centered around some technique to wrap printf.

What would be a nice way to abstract away such repetitive code segments, possibly using preprocessor magic?

Upvotes: 0

Views: 123

Answers (1)

Jeremy Friesner
Jeremy Friesner

Reputation: 73081

My skills aren't enough to attempt variadic macros, but you can remove a lot of the repetitive code via regular old non-variadic macros, like this:

#include <stdio.h>
#include <vector>

#define PARAMS_1 A* a,                                     std::vector<T> args1
#define PARAMS_2 A* a, B* b,                               std::vector<T> args1
#define PARAMS_3 A* a, B* b, C* c,                         std::vector<T> args1
#define PARAMS_4 A* a, B* b, C* c, D* d,                   std::vector<T> args1
#define PARAMS_5 A* a, B* b, C* c, D* d, E* e,             std::vector<T> args1
#define PARAMS_6 A* a, B* b, C* c, D* d, E* e, F* f,       std::vector<T> args1
#define PARAMS_7 A* a, B* b, C* c, D* d, E* e, F* f, G* g, std::vector<T> args1

#define ARGS_1 a
#define ARGS_2 a,b
#define ARGS_3 a,b,c
#define ARGS_4 a,b,c,d
#define ARGS_5 a,b,c,d,e
#define ARGS_6 a,b,c,d,e,f
#define ARGS_7 a,b,c,d,e,f,g

#define DECLAREHEADER(x) template<class T> MyClass(PARAMS_##x)

// preamble: some dummy classes to work with
class A{};
class B{};
class C{};
class D{};
class E{};
class F{};
class G{};

class X{};
class Y{};

// the actual class declaration, located in some header file
class MyClass {
  A* _a;
  B* _b;
  C* _c;
  D* _d;
  E* _e;
  F* _f;
  G* _g;

 public:
  template<class T> void foo(const std::vector<T>& args){};

  MyClass(A* a = NULL, B* b = NULL, C* c = NULL, D* d = NULL, E* e = NULL, F* f = NULL, G* g = NULL);

  DECLAREHEADER(7);
  DECLAREHEADER(6);
  DECLAREHEADER(5);
  DECLAREHEADER(4);
  DECLAREHEADER(3);
  DECLAREHEADER(2);
  DECLAREHEADER(1);
};

// default constructor, this is still fine
MyClass::MyClass(A* a, B* b, C* c, D* d, E* e, F* f, G* g) : _a(a),_b(b),_c(c),_d(d),_e(e),_f(f),_g(g) {}

#define DECLAREALL(x) template<class T> MyClass :: MyClass(PARAMS_##x) : MyClass(ARGS_##x) { foo<T>(args1); }

DECLAREALL(7);
DECLAREALL(6);
DECLAREALL(5);
DECLAREALL(4);
DECLAREALL(3);
DECLAREALL(2);
DECLAREALL(1);

int main(){
  std::vector<X> x;
  MyClass c(NULL,NULL,NULL,x);
  return 1;
}

Note that I omitted the template MyClass<X> and template MyClass<Y> lines because I wasn't sure what their intent was, but you should be able to add them back into the DECLAREALL macro's definition easily enough.

Upvotes: 2

Related Questions