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 Base
在 libbase
和 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
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=CHOICE1
或 MAGIC=CHOICE2
。
- 您不必对所有文件都这样做。当你在文件中使用
MAGIC
,但没有做出选择时,编译器会告诉你。
- 如果
CHOICE1
或 CHOICE2
是您不想指定的主要默认值,您可以使用 -D
为所有文件设置默认值和 -U
+ -D
更改每个文件的决定。
- 如果
CHOICE1
或 CHOICE2
很长,您可以 #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_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 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
是否可以编写一个 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 Base
在 libbase
和 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
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=CHOICE1
或 MAGIC=CHOICE2
。
- 您不必对所有文件都这样做。当你在文件中使用
MAGIC
,但没有做出选择时,编译器会告诉你。 - 如果
CHOICE1
或CHOICE2
是您不想指定的主要默认值,您可以使用-D
为所有文件设置默认值和-U
+-D
更改每个文件的决定。 - 如果
CHOICE1
或CHOICE2
很长,您可以#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_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 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