可以实现BOOST_PP_DEFINED吗?

时间:2016-12-03 04:34:02

标签: c++ boost macros c-preprocessor boost-preprocessor

是否可以编写类似函数的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)的扩展根据ARGMAGIC展开时是否已定义而有所不同:

#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"用于控制导入/导出的宏是为每个库声明一组新的宏,这意味着一个新的标题等等。因此,如果class Base中有libbaseclass Derived中有libderived 1}},然后我们有以下内容:

// 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宏在进行中会有不同的扩展:< / p>

// 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 {时{1}}会扩展为API(LIBBASE),因为__declspec(dllimport)在命令行中,但LIBBASE_COMPILING现在会扩展为API(LIBDERIVED),因为{ {1}}会。但要实现这一点,__declspec(dllexport)宏在上下文中扩展至关重要。

3 个答案:

答案 0 :(得分:4)

看起来您可以使用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;

这种方法存在许多缺陷,例如:如果使用XXX定义#define XXX 1,则无效。 BOOST_VMD_IS_EMPTY本身有limitations

修改

以下是基于API所需的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)

让我们看看预处理器将输出的内容:

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

答案 1 :(得分:2)

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

根据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__

逻辑运算符(compliment and and)

#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

查看令牌是否为parens“()”

的方法
#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()中转入一个arg,其中“IS_PAREN_PROBE()”变为PROBE(〜)变为〜,1。此时我们可以拿起1来自CHECK

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

#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

答案 2 :(得分:-1)

由于您打算使用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 1}},因为actual_contents会自动扩展为<div class="mydiv"> <p>Item to be Centered!</p> </div>