Reputation: 1365
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
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