используйте препроцессор c для автоматической генерации повторяющегося кода

#c #for-loop #preprocessor #variadic-macros

#c #для цикла #препроцессор #переменные-макросы

Вопрос:

В настоящее время я имею дело с фрагментом чрезвычайно повторяющегося кода, который реализует и создает серию конструкторов шаблонов. Общая схема выглядит примерно так:

 // 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>amp; 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;
}
 

Как вы можете видеть, существует определенный тип блока, который повторяется снова и снова

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

Этот код, конечно, кошмар в обслуживании, и я постоянно использую sed его для редактирования кода, что, конечно, является плохой практикой и в какой-то момент неизбежно приведет к сбою.

Я думаю, что повторение можно абстрагировать, используя некоторый макрос препроцессора

 #define GENERATE_CONSTRUCTOR(...) ???

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

Я бы предположил, что можно было бы написать GENERATE_CONSTRUCTOR как некоторый макрос препроцессора с переменным циклом с FOREACH циклом, но, к сожалению, вся документация, которую я смог найти по теме макросов препроцессора с переменным циклом, была для меня очень запутанной и не очень полезной, поскольку большинство примеров сосредоточено вокруг некоторой техники переноса printf .

Какой был бы хороший способ абстрагироваться от таких повторяющихся сегментов кода, возможно, используя магию препроцессора?

Комментарии:

1. boost имеет какой FOREACH -то помощник макроса препроцессора.

2. en.wikipedia.org/wiki/Variadic_template

3. В сторону: почему не один конструктор template<class T> MyClass(std::vector<T> args = {}, A* a = NULL, B* b = NULL, C* c = NULL, D* d = NULL, E* e = NULL, F* f = NULL, G* g = NULL); ?

4. @Caleth: на самом деле хорошее предложение. В конкретном рассматриваемом сценарии это не вариант, потому что A , B , C , и т.д. частично наследуются друг от друга, так что в некоторых случаях конечный объект необходим для устранения запутанных двусмысленностей в способе вызова функции, но в любом случае это не очень хороший дизайн, так что спасибо запредложение!

Ответ №1:

Моих навыков недостаточно, чтобы использовать переменные макросы, но вы можете удалить много повторяющегося кода с помощью обычных старых невариантных макросов, например:

 #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>amp; 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;
}
 

Обратите внимание, что я опустил строки template MyClass<X> и template MyClass<Y> , потому что я не был уверен, каковы их намерения, но вы должны быть в состоянии добавить их обратно в определение DECLAREALL макроса достаточно легко.