BOOST_PP_DEFINED可以实施吗?
Can BOOST_PP_DEFINED be implemented?
是否可以编写一个类似函数的 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.cpp
API(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=CHOICE1
或MAGIC=CHOICE2
即可。
- 您不必对所有文件执行此操作。编译器会告诉您何时在文件中使用了
MAGIC
,但没有做出选择。 - 如果
CHOICE1
或CHOICE2
是您不希望指定的主要默认值,则可以使用-D
为所有文件设置默认值,并使用-U
+-D
更改每个文件的决定。 - 如果
CHOICE1
或CHOICE2
很长,您可以在头文件中#define CHOICE1_TAG actual_contents
最初打算定义MAGIC
的位置,然后使用MAGIC=CHOICE1_TAG
-D
,因为CHOICE1_TAG
将自动扩展为actual_contents
。
- 将成员变量添加到共享库中的类中,不会破坏二进制兼容性吗
- 从不同线程使用int64的不同字节安全吗
- C/C++编译器通常会删除重复的库吗
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 函数调用中参数的顺序重要吗
- 函数向量_指针有不同的原型,我可以构建一个吗
- 如果我只是不访问queue_front节点的子节点,而是将它们推到队列中呢?还是BFS吗
- 你能重载对象变量名本身返回的内容吗
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 有人能分解一下这个c++模板的语法吗
- std::原子加载和存储都需要吗
- 为什么不;名字在地图上是按顺序排列的吗
- C++:将控制台输出存储在宏中更好吗
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- 试试完美的正方形,你能给点小费吗
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- *++*++ppp,*++pp[1],*++(*(1+ppp)有什么具体的区别吗?(C/C++指针问题)
- 施法派生**→基地**有错吗?还有什么选择?
- 我可以举一个现实生活中的例子,其中通过虚空*施法有效而reinterpret_cast无效吗?