Search code examples
c++boostmacrosc-preprocessorboost-preprocessor

Can BOOST_PP_DEFINED be implemented?


Is it possible to write a function-like C preprocessor macro that returns 1 if its argument is defined, and 0 otherwise? Lets call it BOOST_PP_DEFINED by analogy with the other boost preprocessor macros, which we can assume are also in play:

#define BOOST_PP_DEFINED(VAR) ???

#define XXX
BOOST_PP_DEFINED(XXX)  // expands to 1
#undef XXX
BOOST_PP_DEFINED(XXX)  // expands to 0

I'm expecting to use the result of BOOST_PP_DEFINED with BOOST_PP_IIF:

#define MAGIC(ARG) BOOST_PP_IIF(BOOST_PP_DEFINED(ARG), CHOICE1, CHOICE2)

In other words, I want the expansion of MAGIC(ARG) to vary based on whether ARG is defined or not at the time that MAGIC is expanded:

#define FOO
MAGIC(FOO)  // expands to CHOICE1 (or the expansion of CHOICE1)
#undef FOO
MAGIC(FOO)  // expands to CHOICE2 (or the expansion of CHOICE2)

I also found it interesting (and somewhat surprising) that the following doesn't work:

#define MAGIC(ARG) BOOST_PP_IIF(defined(arg), CHOICE1, CHOICE2)

Because apparently defined is only valid in the preprocessor when used as part of an #if expression.

I somewhat suspect that the fact that boost preprocessor doesn't already offer BOOST_PP_DEFINED is evidence for its impossibility, but it can't hurt to ask. Or, am I missing something really obvious about how to achieve this.

EDIT: To add some motivation, here is why I want this. The traditional way to do "API" macros to control import/export is to declare a new set of macros for every library, which means a new header, etc. So if we have class Base in libbase and class Derived in libderived, then we have something like the following:

// base_config.hpp
#if LIBBASE_COMPILING
#define LIBBASE_API __declspec(dllexport)
#else
#define LIBBASE_API __declspec(dllimport)

// base.hpp
#include "base_config.hpp"
class LIBBASE_API base {
public:
    base();
};

// base.cpp
#include "base.hpp"
base::base() = default;

// derived_config.hpp
#if LIBDERIVED_COMPILING
#define LIBDERIVED_API __declspec(dllexport)
#else
#define LIBDERIVED_API __declspec(dllimport)

// derived.hpp
#include "derived_config.hpp"
#include "base.hpp"
class LIBDERIVED_API derived : public base {
public:
    derived();
};

// derived.cpp
#include "derived.hpp"
derived::derived() = default;

Now, obviously, each of the _config.hpp header would really be a lot more complex, defining several macros. We could probably pull out some of the commonalities into a generic config_support.hpp file, but not all. So, in an effort to simplify this mess, I wondered if it would be possible make this generic, so that one set of macros could be used, but that would expand differently based on which _COMPILING macros were in play:

// config.hpp
#define EXPORT __declspec(dllexport)
#define IMPORT __declspec(dllimport)

#define API_IMPL2(COND) BOOST_PP_IIF(COND, EXPORT, IMPORT)()
#define API_IMPL(ARG) API_IMPL2(BOOST_PP_DEFINED(ARG))
#define API(LIB) API_IMPL(LIB ## _COMPILING)

// base.hpp
#include "config.hpp"
class API(LIBBASE) base {
public:
    base();
};

// base.cpp
#include "base.hpp"
base::base() = default;

// derived.hpp
#include "config.hpp"
#include "base.hpp"
class API(LIBDERIVED) derived : public base {
public:
    derived();
};

// derived.cpp
#include "derived.hpp"
derived::derived() = default;

In other words, when compiling base.cpp, API(LIBBASE) would expand to __declspec(dllexport) because LIBBASE_COMPILING was defined on the command line, but when compiling derived.cpp API(LIBBASE) would expand to __declspec(dllimport) because LIBBASE_COMPILING was not defined on the command line, but API(LIBDERIVED) would now expand to __declspec(dllexport) since LIBDERIVED_COMPILING would be. But for this to work it is critical that the API macro expand contextually.


Solution

  • It looks like you could use BOOST_VMD_IS_EMPTY to implement the required behavior. This macro returns 1 if its input is empty or 0 if its input is not empty.

    Trick based on the observation that when XXX is defined by #define XXX, empty parameter list passed to BOOST_VMD_IS_EMPTY(XXX) during expansion.

    Sample implementation of MAGIC macro:

    #ifndef BOOST_PP_VARIADICS
    #define BOOST_PP_VARIADICS
    #endif
    
    #include <boost/vmd/is_empty.hpp>
    #include <boost/preprocessor/control/iif.hpp>
    
    #define MAGIC(XXX) BOOST_PP_IIF(BOOST_VMD_IS_EMPTY(XXX), 3, 4)
    
    #define XXX
    int x = MAGIC(XXX);
    #undef XXX
    int p = MAGIC(XXX);
    

    For Boost 1.62 and VS2015 preprocessor output will be:

    int x = 3;
    int p = 4;
    

    This approach has a number of flaws, e.g. it's not working if XXX defined with #define XXX 1. BOOST_VMD_IS_EMPTY itself has limitations.

    EDIT:

    Here is the implementation of the required API macros based on BOOST_VMD_IS_EMPTY:

    // config.hpp
    #ifndef BOOST_PP_VARIADICS
    #define BOOST_PP_VARIADICS
    #endif
    
    #include <boost/vmd/is_empty.hpp>
    #include <boost/preprocessor/control/iif.hpp>
    
    #define EXPORT __declspec(dllexport)
    #define IMPORT __declspec(dllimport)
    
    #define API_IMPL2(COND) BOOST_PP_IIF(COND, EXPORT, IMPORT)
    #define API_IMPL(ARG) API_IMPL2(BOOST_VMD_IS_EMPTY(ARG))
    #define API(LIB) API_IMPL(LIB ## _COMPILING)
    

    Let's see what preprocessor will output for:

    // base.hpp
    #include "config.hpp"
    class API(LIBBASE) base {
    public:
        base();
    };
    

    When LIBBASE_COMPILING defined, GCC output:

    class __attribute__((dllexport)) Base
    {
      public:
        Base();
    }; 
    

    When LIBBASE_COMPILING is not defined, GCC output:

    class __attribute__((dllimport)) Base
    {
      public:
        Base();
    };
    

    Tested with VS2015 and GCC 5.4 (Cygwin)

    EDIT 2: As @acm mentioned when parameter defined with -DFOO it's same as -DFOO=1 or #define FOO 1. In this case approach based on BOOST_VMD_IS_EMPTY is not working. To overcome it you can use BOOST_VMD_IS_NUMBER (thnx to @jv_ for the idea). Implementation:

    #ifndef BOOST_PP_VARIADICS
    #define BOOST_PP_VARIADICS
    #endif
    
    #include <boost/vmd/is_number.hpp>
    #include <boost/preprocessor/control/iif.hpp>
    
    #define EXPORT __declspec(dllexport)
    #define IMPORT __declspec(dllimport)
    
    #define API_IMPL2(COND) BOOST_PP_IIF(COND, EXPORT, IMPORT)
    #define API_IMPL(ARG) API_IMPL2(BOOST_VMD_IS_NUMBER(ARG))
    #define API(LIB) API_IMPL(LIB ## _COMPILING)