C Preprocessor: Evaluate macro early

2020-03-09 09:52发布

问题:

Consider the following setup:

a.h

#define A 5
#define B A
#undef A
#define A 3

a.c

#include "a.h"
#include <stdio.h>

int main()
{
    printf("%d\n", B);
    return 0;
}

While this very reasonably prints 3, is there a way to make it print 5, i.e. do the substitution of 5 for A already at line two of a.h?

回答1:

No, there's no way to do that. Unless you know all the possible values of A, and they are always integers, in which case you can laboriously test each one in turn:

#if A == 0
# define B 0
#elif A == 1
# define B 1
#elif A == 2
# define B 2
/*  ... and a very long etc. */
#endif

If your use case only involves integers, you have more options. You could, for example, declare Bto be static const int or enum (depending on language) instead of a macro, which would obviously use the current value of the macro. If you really really want macros, the Boost preprocessing library has an implementation of the laborious sequence of #ifs above (with some cleverness to reduce the number of preprocessor statements needed to log(N) instead of N).


There is no macro substitution in the #define preprocessor directive; this fact is covered by §6.10 para. 7 of the C standard (§16 para. 6 of the C++ standard, with identical wording):

The preprocessing tokens within a preprocessing directive are not subject to macro expansion unless otherwise stated.

In the description of the #if and #include directives, the standard specifies that macro replacement does occur, which is why the #if solution above works (and the Boost implementation, which also uses a computed #include).



回答2:

Yes. Boost's preprocessor library (a set of portable includes, not an extended preprocessor) includes support for "mutable" macro definitions. Instead of defining a macro to expand to a value directly, you can define it to expand to a reference a mutable slot, which can be changed because it expands the value "assigned" to it early. In this case you're less interested in the ability to change the value, than in the fact that this early expansion means it can grab the value out of A at a point ahead of both the use of B or the redefinition of A.

#include <boost/preprocessor/slot/slot.hpp>

#define A 5
#define B BOOST_PP_SLOT(1)

// "assign" A to B
#define BOOST_PP_VALUE A
#include BOOST_PP_ASSIGN_SLOT(1)

#undef A
#define A 3

#include "a.h"
#include <stdio.h>

int main()
{
    printf("%d\n", B);  // 5
    return 0;
}

Support is limited to integers. It takes advantage of the fact that #if directives force expansion of any contained macros (so do #line and #error, although those are not very useful for this purpose), and uses them to build up an equivalent integer value for the slot being assigned to, stored in hidden backend macros. This way it can "extract" a value from A, and B can then refer to the value itself even if A changes or is removed.