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.
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)