1

I have a C++ program where a certain pattern of blocks of code keeps repeating, and I am wondering if I can use C++ MACROS for the preprocessor to auto-generate this code. To be more precise I have blocks of code which look something like this:

for(std::size_t i=0;i< lc_.size(); i++)
{
  std::string str;
  state::MsData lc = data->lc(i);
  convert<data::ClassForLC>(lc.data(), str);
  lc_[i] = data::ClassForLC(str);
}

I then may have another block that looks like this:

for(std::size_t i=0;i< mmop_.size(); i++)
{
  std::string str;
  state::MvData mmop = data->mmop(i);
  convert<data::ClassForMMOP>(mmop.data(), str);
  mmop_[i] = data::ClassForMMOP(str);
}

As you can see the general pattern is something like this:

  for(std::size_t i=0;i< X_.size(); i++)
    {
      std::string str;
      Y X = data->X(i);
      convert<Z>(X.data(), str);
      X_[i] = Z(str);
    }

I was wondering if it is possible to define a macro REPLACE(X,Y,Z) which replaces X,Y,Z in the code above with whatever text I pass as parameter? Note: I use C++11. Thanks

astrophobia
  • 839
  • 6
  • 13
  • Does this answer your question? [Multi line preprocessor macros](https://stackoverflow.com/questions/10419530/multi-line-preprocessor-macros) – Lala5th Aug 13 '21 at 18:26
  • 4
    It's possible - but don't do it ! It's a maintenance pain !. Also `for(std::size_t i=0;i< lc_.size(); i++)` length doesn't change in the loop so don't grab it each time through - depending on the collection `.size()` might be an expensive operation. – John3136 Aug 13 '21 at 18:27
  • 3
    Just to add on to what @John3136 said, debugging becomes a huge nightmare. Instead, consider templated functions or passing in a function to do the non-generic part. – crashmstr Aug 13 '21 at 18:29
  • 2
    It's possible to generalize code like this so it only has to be written once. Function templates are a common solution. It's also possible to write code with macros, but you then risk all the pain that comes from using macros. – Drew Dormann Aug 13 '21 at 18:47
  • 2
    What happened to the ancient art of placing duplicate code into a function or subroutine? Rather than creating macros, try defining the code in a function, place into a header file and tag it with "inline". – Thomas Matthews Aug 13 '21 at 18:48

3 Answers3

5

Don't do that.

template<class Z, class X, class F>
void do_stuff( X&& x, F&& f ) {
  for(std::size_t i=0;i< x.size(); i++)
  {
    std::string str;
    auto tmp = f(i);
    convert<Z>(tmp.data(), str);
    x[i] = Z(str);
  }
}

write a template. We can use it like this:

do_stuff<data::ClassForMMOP>(
  mmop_,
  [&](std::size_t i){ return data->mmop(i); }
);

doing this with macros is possible, it is just a bad idea.

It is a bad idea for a number of reasons:

  1. The errors you get will be text substitution based.

  2. Debugging will often be impossible.

  3. There are lots of subtle bugs that can crop up in macro based generation that are very hard to spot.

With templates, you get something that the C++ compiler and debugger fully understands, and a bunch of "early" checks on correctness. The errors generated by templates are difficult, but infinitely easier than the macro errors.

But to actually do it in a macro:

#define MACRO(X,Y,Z) \
  X x = Y(); \
  Z(Y)

use \ before the newlines to make "everything on the same line" as far as the C++ preprocessor (well, more than that) is concerned.

In effect you are generating one extremely long bit of C++ code. As C++ does not rely on newlines, the macro works.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Of all the reasons to not use simple macros, bad error reporting is really not the one. First of, most modern compilers are preprocessors themselves and they usually accurately report errors in macros (with text like "While expanding macro..."), second, nothing beats errors generated by templated code. The points about debugging and subtle bugs stand. – SergeyA Aug 13 '21 at 19:06
  • @SergeyA I find that simple template error messages are easy. Templates just permit much more complex structures. Doing anything that complex in a pile of recursive macros would make a many times harder to understand error messages, in my experience. It was really painful. (source of pain: bug in reflection system based off complex chained macros.) Maybe things have gotten better, dunno. – Yakk - Adam Nevraumont Aug 13 '21 at 19:14
  • I'd say, for simple stuff, both macro and template errors are quite readable nowadays. For anything not so simple, they are both hard. – SergeyA Aug 13 '21 at 19:20
  • 1
    `decltype(f(i)) tmp = f(i);` -> `auto tmp = f(i);` – Paul Sanders Aug 13 '21 at 19:26
  • @PaulSanders Ah, I forgot they had that use of `auto` way back in [tag:C++11]. I got so use to "crap, C++11, I gotta remove `auto`". Sigh. – Yakk - Adam Nevraumont Aug 13 '21 at 20:10
1

Try:

#define MY_MACRO(X, Y, Z)                   \
    for(std::size_t i=0;i< X#_.size(); i++) \
    {                                       \
      std::string str;                      \
      Y X = data->X(i);                     \
      convert<Z>(X.data(), str);            \
      X#_[i] = Z(str);                      \
    }

But I suggest using template instead.

Top-Master
  • 7,611
  • 5
  • 39
  • 71
1

You can use a backslash (\) to extend the macro definition by another line. For example if I had:

#define MACRO1(x) std::cout << "macro1: " << x << std::endl;

I could change it to:

#define MACRO1(x) std::cout << "macro1: " \
<< x << std::endl;

You also need to make sure you dont use a backslash on the last line of you definition and always leave a space before you put the backslash.

EDIT:

You can also use templates normally like:

#define my_template<int T> std::cout << T << std::endl;

But I recommend you do this instead:

#define my_template(T) std::cout << T << std::endl;

because:

  1. The errors you get will be text substitution based.
  2. Debugging will usually not be possible.
  3. There are lots of small bugs that can crop up in macro based generation that are very difficult to spot.
anom1
  • 53
  • 1
  • 11