【问题标题】:Can BOOST_PP_DEFINED be implemented?可以实现 BOOST_PP_DEFINED 吗?
【发布时间】:2017-04-18 01:48:39
【问题描述】:

是否可以编写一个类似函数的 C 预处理器宏,如果其参数已定义,则返回 1,否则返回 0?让我们称之为 BOOST_PP_DEFINED 与其他 boost 预处理器宏类比,我们可以假设它们也在起作用:

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

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

// 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),因为@ 987654352@ 没有在命令行上定义,但 API(LIBDERIVED) 现在将扩展为 __declspec(dllexport),因为 LIBDERIVED_COMPILING 将是。但要使其发挥作用,API 宏必须根据上下文展开。

【问题讨论】:

  • 在一般情况下可能是不可能的,但在有限的情况下是可能的。您是否控制 FOO#define'd 的方式?是你在各个地方#define它吗? FOO 是什么类型 - 它定义了字符串、整数、空定义还是其他?
  • @Codeguard 我确实控制了FOO 的定义方式,并且在我设想的用法中,当某些文件被编译时,我将使用 -D 在编译行上设置它。因此,我可以将其设置为您提到的任何选项。
  • 如果你可以选择FOO的值,为什么不直接做-DFOO=1,然后加一点#ifndef FOO / #define FOO 0 / #endif,就用FOO
  • 如果#ifndef FOO / #define FOO 0 / #endif 进入预编译头文件,这将中断。
  • @rodrigo 我添加了一个示例来帮助激发我为什么需要上下文扩展,以及为什么只定义和使用 #if 不起作用。

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


【解决方案1】:

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

编辑:

这里是基于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(这个想法请转至@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)

【讨论】:

  • 很有趣,我今天就试试这个,看看能不能让它工作。
  • 我认为使用#define XXX 1 定义XXX 时的限制可以使用BOOST_VMD_IS_NUMBER 轻松解决(类似于您使用BOOST_VMD_IS_EMPTY 的方法)。 Here 是一个可能的例子(未使用 VS 测试)。
  • @acm 它对你有用吗?这很有趣。我期待着您的回复。
  • 我第一次尝试时无法让它工作,但没有太多时间来解决这个问题。有空我一定会更新的。
  • 是的,完全使用BOOST_VMD_IS_NUMBER。如果您愿意接受预处理器元编程,我认为这可能是一种更好的方式来实现项目范围的可见性宏。在由大量库组成的项目中,需要为每个库创建单独的 config.h 头文件并不好玩。这样,做一次,让预处理器完成繁重的工作。
【解决方案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__

逻辑运算符(恭维和与)

#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() 中变成了一个 arg,而“IS_PAREN_PROBE ()”变成了 PROBE(~),它变成了 ~, 1。此时我们可以拿起 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。如果不是,我们就不平等,吃下面的 args。

#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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

    • 我不确定我是否喜欢这个问题的目标,但这个解决方案具有将过去在程序源代码中的配置推送到构建系统源 (Makefile) 中的不幸特性。
    • OP 说他将在他设想的用法中使用-D
    • @Codeguard 请查看我更新的问题,详细了解为什么我需要宏在同一 TU 内的不同上下文中以不同方式扩展。
    猜你喜欢
    • 2017-12-17
    • 2012-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-14
    相关资源
    最近更新 更多