BOOST_PP_DEFINED可以实现吗?

Can BOOST_PP_DEFINED be implemented?

是否可以编写一个 function-like C 预处理器宏,如果定义了参数,则 returns 1,否则 0?让我们通过类比其他升压预处理器宏来称呼它 BOOST_PP_DEFINED,我们可以假设它们也在发挥作用:

#define BOOST_PP_DEFINED(VAR) ???

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

我希望将 BOOST_PP_DEFINED 的结果与 BOOST_PP_IIF:

一起使用
#define MAGIC(ARG) BOOST_PP_IIF(BOOST_PP_DEFINED(ARG), CHOICE1, CHOICE2)

换句话说,我希望 MAGIC(ARG) 的扩展根据 ARG 在扩展 MAGIC 时是否定义而变化:

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

我还发现以下 不起作用 很有趣(并且有点令人惊讶):

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

因为显然 defined 仅在用作 #if 表达式的一部分时在预处理器中有效。

我有点怀疑升压预处理器还没有提供 BOOST_PP_DEFINED 的事实证明了它是不可能的,但问问也无妨。或者,关于如何实现这一点,我是否遗漏了一些非常明显的东西。

编辑:为了增加一些动力,这就是我想要这个的原因。做"API"宏来控制import/export的传统方法是为每个库声明一组新的宏,这意味着一个新的header,等等。所以如果我们有class Baselibbaseclass Derivedlibderived 中,然后我们有如下内容:

// 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;

现在,很明显,_config.hpp header 中的每一个确实要复杂得多,定义了几个宏。我们或许可以将一些共性提取到通用 config_support.hpp 文件中,但不是全部。因此,为了简化这个混乱局面,我想知道是否有可能使它成为通用的,以便可以使用一组宏,但这会根据正在使用的 _COMPILING 宏进行不同的扩展:

// 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;

也就是说,编译base.cpp时,API(LIBBASE)会扩展为__declspec(dllexport),因为LIBBASE_COMPILING是在命令行定义的,但编译时derived.cpp API(LIBBASE) 将扩展为 __declspec(dllimport) 因为 LIBBASE_COMPILING 而不是 在命令行上定义,但 API(LIBDERIVED) 现在将扩展为 __declspec(dllexport) 因为 LIBDERIVED_COMPILING 会。但要使其发挥作用,API 宏根据上下文进行扩展至关重要。

既然你打算使用FOO作为你控制的文件级开关,我建议你使用一个更简单的解决方案。建议的解决方案更易于阅读,不那么令人惊讶,不需要肮脏的魔法。

而不是 #define MAGIC(ARG) BOOST_PP_IIF(BOOST_PP_DEFINED(ARG), CHOICE1, CHOICE2),您只需 -D 每个文件 MAGIC=CHOICE1MAGIC=CHOICE2

  • 您不必对所有文件都这样做。当你在文件中使用 MAGIC,但没有做出选择时,编译器会告诉你。
  • 如果 CHOICE1CHOICE2 是您不想指定的主要默认值,您可以使用 -D 为所有文件设置默认值和 -U + -D 更改每个文件的决定。
  • 如果 CHOICE1CHOICE2 很长,您可以 #define CHOICE1_TAG actual_contents 在您最初打算定义 MAGIC 的头文件中,然后使用 -D MAGIC=CHOICE1_TAG,因为CHOICE1_TAG会自动扩展成actual_contents

看起来您可以使用 BOOST_VMD_IS_EMPTY 来实现所需的行为。此宏 returns 1 如果其输入为空或 0 如果其输入不为空。

基于以下观察的技巧:当 XXX#define XXX 定义时,空参数列表在扩展期间传递给 BOOST_VMD_IS_EMPTY(XXX)

MAGIC 宏的示例实现:

#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);

对于 Boost 1.62 和 VS2015 预处理器输出将是:

int x = 3;
int p = 4;

这种方法有很多缺陷,例如如果 XXX 定义为 #define XXX 1,则它不起作用。 BOOST_VMD_IS_EMPTY本身有limitations.

编辑:

这里是基于BOOST_VMD_IS_EMPTYAPI宏的实现:

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

让我们看看预处理器将输出什么:

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

定义LIBBASE_COMPILING时,GCC输出:

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

LIBBASE_COMPILING没有定义时,GCC输出:

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

已使用 VS2015 和 GCC 5.4 (Cygwin) 进行测试

编辑 2: 正如@acm 在使用 -DFOO 定义参数时提到的那样,它与 -DFOO=1#define FOO 1 相同。在这种情况下,基于 BOOST_VMD_IS_EMPTY 的方法不起作用。要克服它,您可以使用 BOOST_VMD_IS_NUMBER(thnx to @jv_ for the idea)。实施:

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

这不是纯粹的定义检查,但我们可以一路检查特定的令牌名称。

基于 Paul Fultz II 的 Cloak 注释第一原理解决方案:

首先提供根据宏扩展到 0 或 1 有条件地选择文本的能力

#define IIF(bit) PRIMITIVE_CAT(IIF_, bit)
#define IIF_0(t, f) f
#define IIF_1(t, f) t

基本串联

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a##__VA_ARGS__

逻辑运算符(恭维与和)

#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0
#define BITAND(x) PRIMITIVE_CAT(BITAND_, x)
#define BITAND_0(y) 0
#define BITAND_1(y) y

一种查看标记是否为括号“()”的方法

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0, )
#define PROBE(x) x, 1,
#define IS_PAREN(x) CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_PROBE(...) PROBE(~)

注意 IS_PAREN 有效是因为 "IS_PAREN_PROBE X" 在 CHECK() 中变成一个参数,而 "IS_PAREN_PROBE ()" 变成 PROBE(~) 变成 ~, 1。我们可以从 CHECK

中取出 1

另一个根据需要吃掉一些宏参数的实用程序

#define EAT(...)

在这里,我们利用蓝色绘画(防止简单递归宏的东西)来检查两个标记是否相同。如果是,则折叠为 ()。否则不是,我们可以通过 IS_PAREN.

检测到

这依赖于任何给定符号的 COMPARE_XXX 身份宏

#define PRIMITIVE_COMPARE(x, y) IS_PAREN(COMPARE_##x(COMPARE_##y)(()))

我们为该助手添加了一个 IS_COMPARABLE 特征

#define IS_COMPARABLE(x) IS_PAREN(CAT(COMPARE_, x)(()))

我们通过检查两个 args 是否可比较,然后转换为 primitive_compare 如果它们是可比较的,则向后工作到 EQUAL。如果不是,则我们不相等并吃掉以下参数。

#define NOT_EQUAL(x, y)                             \
    IIF(BITAND(IS_COMPARABLE(x))(IS_COMPARABLE(y))) \
    (PRIMITIVE_COMPARE, 1 EAT)(x, y)

EQUAL 是恭维

#define EQUAL(x, y) COMPL(NOT_EQUAL(x, y))

最后,我们真正想要的宏。

首先我们为 "BUILDING_LIB"

启用比较
#define COMPARE_BUILDING_LIB(x) x

然后是我们实际的决定宏,它是一个整数,如果一个符号是否解析为 "BUILDING_LIB"

#define YES_IF_BUILDING_LIB(name) IIF(EQUAL(name, BUILDING_LIB))("yes", "no")

#include <iostream>

#define FOO BUILDING_LIB

int main(int, char**) {
    std::cout << YES_IF_BUILDING_LIB(FOO) << "\n";
    std::cout << YES_IF_BUILDING_LIB(BAR) << "\n";
}

输出:

yes
no

查看他的精彩博文(我抄袭自):C Preprocessor tricks, tips, and idioms