Avoid expansion of macros while using boost preprocessor sequences

Viewed 469

I'm trying to get the OS and compiler name as a string in C++. Although there are many questions about this I did not find a definitive answer. So I tried to use Boost.Predef 1.55 which defines macros of the type BOOST_OS_<OS> and BOOST_OS_<OS>_NAME.

Hence one could simply do if(BOOST_OS_<OS>) return BOOST_OS_<OS>_NAME; for every OS boost supports. Same for compilers with COMP instead of OS. To avoid the repetition I wanted to use Boost.Preprocessor and put them all in a loop.

What I came up with is this:

#define MAKE_STMT_I2(PREFIX) if(PREFIX) return PREFIX ## _NAME;
#define MAKE_STMT_I(type, curName) MAKE_STMT_I2(BOOST_ ## type ## _ ## curName)
#define MAKE_STMT(s, type, curName) MAKE_STMT_I(type, curName)
#define OS_LIST (AIX)(AMIGAOS)(ANDROID)(BEOS)(BSD)(CYGWIN)(HPUX)(IRIX)(LINUX)(MACOS)(OS400)(QNX)(SOLARIS)(UNIX)(SVR4)(VMS)(WINDOWS)(BSDI)(DRAGONFLY)(BSD_FREE)(BSD_NET)(BSD_OPEN)

    BOOST_PP_SEQ_FOR_EACH(MAKE_STMT, OS, OS_LIST)

However I run into problems where the values are expanded to soon. E.g. VMS defines already a macro named VMS which then gets replaced in OS_LIST. Even doing something like #define OS_LIST (##AIX##)(##AMIGAOS##)(... does not help as it seems to get expanded in boost later.

How can I avoid the expansion in the sequence completely?

2 Answers

Since you rely on the token VMS being undefined, a quick solution is a simple #undef VMS. Obviously, to avoid breaking code which relies on that macro, you should put your Boost PP code in its own .cpp file.

How can I avoid the expansion in the sequence completely?

You can't. Passing high level data structures as an argument to a macro necessarily involves evaluating the data structure.

You could avoid this problem and still use the boost macros in basically three ways:

1. Undefine problem macros before the call

This is essentially what MSalters recommended.

The idea being that if VMS isn't defined, its evaluation won't expand it.

Here, you risk VMS being left undefined, which could have dire consequences, so you have to mitigate that (MSalters touched on this).

2. Build high level macros from different data

2 might for example use:

#define OS_LIST (S_AIX)(S_BEOS)(S_VMS)

...and require you to change your MAKE_STMT macro complex; for example, this:

#define MAKE_STMT_I2(PREFIX) if(PREFIX) return PREFIX ## _NAME;
#define MAKE_STMT_I(curName) MAKE_STMT_I2(BOOST_O ## curName)
#define MAKE_STMT(s, type, curName) MAKE_STMT_I(curName)
#define OS_LIST (S_AIX)(S_AMIGAOS)(S_ANDROID)(S_BEOS)(S_BSD)(S_CYGWIN)(S_HPUX)(S_IRIX)(S_LINUX)(S_MACOS)(S_OS400)(S_QNX)(S_SOLARIS)(S_UNIX)(S_SVR4)(S_VMS)(S_WINDOWS)(S_BSDI)(S_DRAGONFLY)(S_BSD_FREE)(S_BSD_NET)(S_BSD_OPEN)

(Note: Here I'm ignoring the type; it's not necessary to pass OS in as data to the iteration sequence anyway).

The idea here is to find a different shared portion of BOOST_OS_FOO and BOOST_OS_FOO_NAME to put in your data, so that your data doesn't include the macros you're defining.

Here, you risk S_FOO being defined at some higher level messing you up. You could mitigate this by finding a different piece to use in your data.

3. Build wrapper identifiers

This is easiest to define by example:

#define OS_LIST (AIX)(BEOS)(8VMS)
#define BOOST_OS_8VMS BOOST_OS_VMS
#define BOOST_OS_8VMS_NAME BOOST_OS_VMS_NAME

The idea here is that you're building different BOOST_OS_xxx / BOOST_OS_xxx_NAME form macros, then remapping those back to the desired ones. Using a numeric prefix has the advantage of becoming immune to expansion (such entities are valid preprocessor tokens (pp-numbers), but they cannot be object-like macros).

Related