BOOST_PP_DEFINED可以实施吗?

Can BOOST_PP_DEFINED be implemented?

本文关键字:施吗 PP DEFINED BOOST      更新时间:2023-10-16

是否可以编写一个类似函数的 C 预处理器宏,如果定义了参数,则返回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)的扩展根据在扩展MAGIC时是否定义了ARG而有所不同:

#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 预处理器尚未提供BOOST_PP_DEFINED这一事实证明了它是不可能的,但问一下也无妨。或者,我是否错过了一些关于如何实现这一目标的非常明显的东西。

编辑:为了增加一些动力,这就是我想要这个的原因。执行"API"宏来控制导入/导出的传统方法是为每个库声明一组新的宏,这意味着一个新的标头等。因此,如果我们在libbase中有class Base,在libderived中有class Derived,那么我们有如下所示的内容:

// 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标头实际上要复杂得多,定义了几个宏。我们可能可以将一些共性提取到一个通用的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.cppAPI(LIBBASE)会扩展到__declspec(dllimport),因为LIBBASE_COMPILING不是在命令行上定义的,但是API(LIBDERIVED)现在会扩展到__declspec(dllexport),因为LIBDERIVED_COMPILING会。但要做到这一点,API宏在上下文中展开是至关重要的。

看起来您可以使用BOOST_VMD_IS_EMPTY来实现所需的行为。如果此宏的输入为空,则返回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;

这种方法有许多缺陷,例如,如果用#define XXX 1定义XXX它不起作用。BOOST_VMD_IS_EMPTY本身也有局限性。

编辑:

以下是基于BOOST_VMD_IS_EMPTY的所需API宏的实现:

// 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@jv_这个想法)。实现:

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

我们通过检查两个参数是否可比,然后转换为primitive_compare(如果它们)来向后工作到相等。 如果不是,我们就不平等,吃以下参数。

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

平等是恭维

#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 预处理器技巧、技巧和习语

由于您打算将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的位置,然后使用MAGIC=CHOICE1_TAG-D,因为CHOICE1_TAG将自动扩展为actual_contents