Вопрос

Reading some C++ code I came across what I'll call a "functional" use of function Macros roughly as follows (this is a totally stylized example to make the point):

#define TOP_LEVEL(ARG1)     \
ARG1("foo1","bar1")     \
ARG1("foo2","bar2")

#define NEXT_LEVEL(ARG2A, ARG2B)    \
cout << ARG2A << " and " << ARG2B;

TOP_LEVEL(NEXT_LEVEL)

I'm relatively new to the language and at first I couldn't figure this out, but then I ran it through just the preprocessor (g++ -E) and lo and behold it resolves to:

cout << "foo1" << " and " << "bar1"; cout << "foo2" << " and " << "bar2";

Do you see what it did there? It passed the Macro NEXT_LEVEL like a function pointer to the Macro TOP_LEVEL. Seeing how useful this could potentially be, I wanted to learn more about it: passing around functions to other functions is pretty sophisticated stuff and there must be at least something more to say about the technique.

Yet despite a ton of Googling I can't find evidence that this feature of the preprocessor even exists, let alone anything approaching documentation: here, here, here and here are just four examples of Macro tutorials that skip right past this; the last even has a section called "Advanced Macro tricks" - surely this qualifies!?

(Please note this is totally different than simply calling a function macro with another evaluated function macro as an argument- FOO(BAR(2)) is much more straightforward.)

My questions are:

  • Is there an actual name for this behavior?
  • Is it documented anywhere?
  • It is commonly used, or are there well known pitfalls, etc.?
Это было полезно?

Решение

The idea is coined "X-Macro". Some definitions won't include your particular example (X-macros generally are a bit more involved, with a file being included), but any relevant info. about this will fall under that term when searching.

As chris mentioned in the comments, Boost.Preprocessor uses this idea to great effect. Popular uses are: BOOST_PP_REPEAT, BOOST_PP_LIST_FOR_EACH, and most powerfully: BOOST_PP_ITERATE.

BOOST_PP_ITERATE is a "true" X-Macro; including a single file is expands to something dependent on a macro defined just prior. I show a more "proper" skeleton framework in this other answer, but an example would be:

// in xyz_data.def
DEFINE_XYZ(foo, 1, "Description A")
DEFINE_XYZ(bar, 5, "Description B")
DEFINE_XYZ(baz, 7, "Description C")

Then later when I just want column 1 I can do:

#define DEFINE_XYZ(name, number, desc) some_func(name)
#include "xyz_data.def"

And somewhere else where I want to generate some function for each one, I can do:

#define DEFINE_XYZ(name, number, desc)                              \
    int BOOST_PP_CAT(get_number_for_, name)()                       \
    {                                                               \
       std::clog << "Getting number, which is: " desc << std::endl; \
                                                                    \
       return number;                                               \
    }

#include "xyz_data.def"

You can then generate an enum where the name equals the number, etc.

The power is that when I want to add a new xyz, I just add it in one spot and it magically shows up everywhere it needs to be. I have done something like this in a very large codebase to keep some bookmarking data in one central place, but the various attributes were used differently in various locations.

Note that there is often no way around this; what I have are syntactically different, so no other language feature will generalize it for me to that level, only macros. Macros are not evil.

What you have is effectively an X-macro where the .def file is self-contained enough to be a #define. In other words, #include "xyz_data.def" is just TOP_LEVEL.

There is only one large downside to this, and ironically it's not the use of X-macros themselves but the effect they have on C and C++ compilers. The problem is that the preprocessor has allowed us to change the preprocessed result of a file every time its included, even if the file contents are exactly the same.

You may have heard that C and C++ are slow to compile compared to modern languages, this is one of the reasons why. It has no proper module/packaging system, just ad-hoc inclusion of other files. And we just learned, in general this cannot be avoided. Oops. (That said, compilers are smart and will note when you have include guards around a file, for example, and avoid processing it multiple times. But this is situational.)

That said, using X-Macros themselves shouldn't be a huge contributor to the compilation time of a real program. It's just that their mere potential existence reaches out into the real word and screws with compiler's heads.

Другие советы

Here is a few lectures : C is purely functionnal,

I suggest you take a look at libpp, macrofun,

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top