版权所有 © 2009-2012 Lorenzo Caminiti
根据 Boost 软件许可证 1.0 版分发(参见随附文件 LICENSE_1_0.txt 或 https://boost.ac.cn/LICENSE_1_0.txt 上的副本)
此库允许将类型包装在圆括号中,以便它们始终可以作为宏参数传递。
考虑以下声明名为 var
n
的变量并指定其 type
的宏(另见 var_error.cpp
)
#define VAR(type, n) type var ## n VAR(int, 1); // OK. VAR(std::map<int, char>, 2); // Error.
第一次宏调用可以正确声明名为 var1
的 int 类型变量。但是,第二次宏调用失败,并产生类似于以下内容的预处理器错误:
error: macro "VAR" passed 3 arguments, but takes just 2
这是因为作为第一个宏参数传递的 std::map
类型包含一个未用圆括号 ()
包裹的逗号 ,
。预处理器将未包装的逗号解释为宏参数之间的分隔符,从而得出结论:按以下顺序向宏传递了三个(而不是两个)参数:
std::map<int
char>
2
请注意,与编译器不同,预处理器仅识别圆括号 ()
。在解析宏参数时,预处理器不识别尖括号 <>
和方括号 []
。
在某些情况下,可以通过完全避免将类型表达式传递给宏来解决此问题。例如,在上述情况下,可以使用 typedef
来指定类型表达式,其中逗号位于宏之外(另见 var.cpp
)
typedef std::map<int, char> map_type; VAR(map_type, 3); // OK.
当这既不可能也不理想时(例如,参见下面的函数模板 f
),此库头文件 boost/utility/identity_type.hpp
定义了一个宏 BOOST_IDENTITY_TYPE
,它可用于解决此问题,同时将类型表达式保留为宏参数之一(另见 var.cpp
)。
#include <boost/utility/identity_type.hpp> VAR(BOOST_IDENTITY_TYPE((std::map<int, char>)), 4); // OK.
BOOST_IDENTITY_TYPE
宏扩展为一个表达式,该表达式(在编译时)计算为指定的类型。指定的类型永远不会拆分为多个宏参数,因为它始终用一组额外的圆括号 ()
包裹。实际上,必须使用两组圆括号:调用宏的括号 BOOST_IDENTITY_TYPE(...)
以及包裹传递给宏的类型的内部括号 BOOST_IDENTITY_TYPE((...))
。
此宏适用于任何 C++03 编译器(并且它不使用 可变参数宏)。[1] 作者最初使用 GNU 编译器集合 (GCC) C++ 4.5.3(启用和禁用 C++11 功能 -std=c++0x
)在 Cygwin 上以及在 Windows 7 上使用 Miscrosoft Visual C++ (MSVC) 8.0 开发和测试了此库。有关支持的编译器和平台的更多信息,请参见库的 回归测试结果。
在模板中使用此宏时,必须在其前面加上 typename
。例如,让我们编写一个声明名为 arg
n
的函数参数并指定其 type
的宏(另见 template.cpp
)
#define ARG(type, n) type arg ## n template<typename T> void f( // Prefix macro with `typename` in templates. ARG(typename BOOST_IDENTITY_TYPE((std::map<int, T>)), 1) ) { std::cout << arg1[0] << std::endl; }
std::map<int, char> a; a[0] = 'a'; f<char>(a); // OK... // f(a); // ... but error.
但是,请注意,在调用函数时,必须手动指定模板参数 char
,例如 f<char>(a)
。实际上,当使用 BOOST_IDENTITY_TYPE
宏包装函数模板参数时,编译器将无法再从函数调用中自动推断模板参数,例如 f(a)
那样。[2](此限制不适用于类模板,因为类模板参数必须始终显式指定。)换句话说,如果不使用 BOOST_IDENTITY_TYPE
宏,C++ 通常能够自动推断函数模板参数,如下所示:
template<typename T> void g( std::map<int, T> arg1 ) { std::cout << arg1[0] << std::endl; }
g<char>(a); // OK... g(a); // ... and also OK.
在某些编译器(例如 GCC)上,对抽象类型(即具有一个或多个纯虚函数的类)使用此宏会生成编译器错误。可以通过操作类型并添加和删除对它的引用来避免这种情况。
让我们编写一个对 模板元编程 (TMP) 元函数执行静态断言的宏(类似于 Boost.MPL BOOST_MPL_ASSERT
)。BOOST_IDENTITY_TYPE
宏可用于将具有多个模板参数的元函数传递给 assert 宏(以便处理分隔模板参数的逗号)。在这种情况下,如果元函数是抽象类型,则需要通过添加和删除对它的引用来操作它(另见 abstract.cpp
)
#define TMP_ASSERT(metafunction) \ BOOST_STATIC_ASSERT(metafunction::value) template<typename T, bool b> struct abstract { static const bool value = b; virtual void f(T const& x) = 0; // Pure virtual function. }; TMP_ASSERT( boost::remove_reference< // Add and remove BOOST_IDENTITY_TYPE(( // reference for boost::add_reference< // abstract type. abstract<int, true> >::type )) >::type );
BOOST_IDENTITY_TYPE
宏既可以在调用用户定义宏时使用(如之前的示例所示),也可以在实现用户定义宏时内部使用(如下所示)。当在用户定义宏的实现中使用 BOOST_IDENTITY_TYPE
时,用户宏的调用者将必须指定额外的括号(另见 paren.cpp
)
#define TMP_ASSERT_PAREN(parenthesized_metafunction) \ /* use `BOOST_IDENTITY_TYPE` in macro definition instead of invocation */ \ BOOST_STATIC_ASSERT(BOOST_IDENTITY_TYPE(parenthesized_metafunction)::value) #define TMP_ASSERT(metafunction) \ BOOST_STATIC_ASSERT(metafunction::value) // Specify only extra parenthesis `((...))`. TMP_ASSERT_PAREN((boost::is_const<std::map<int, char> const>)); // Specify both the extra parenthesis `((...))` and `BOOST_IDENTITY_TYPE` macro. TMP_ASSERT(BOOST_IDENTITY_TYPE((boost::is_const<std::map<int, char> const>)));
但是,请注意,即使宏参数不包含逗号,调用者也始终必须指定额外的括号
TMP_ASSERT_PAREN((boost::is_const<int const>)); // Always extra `((...))`. TMP_ASSERT(boost::is_const<int const>); // No extra `((...))` and no macro.
在某些情况下,在用户定义宏的实现中使用 BOOST_IDENTITY_TYPE
可能会为调用者提供最佳语法。例如,对于 BOOST_MPL_ASSERT
就是这种情况,因为大多数模板元编程表达式都包含未包装的逗号,因此对于用户来说,始终指定额外的括号 ((...))
比使用 BOOST_IDENTITY_TYPE
更不容易混淆。
BOOST_MPL_ASSERT(( // Natural syntax. boost::mpl::and_< boost::is_const<T> , boost::is_reference<T> > ));
但是,在其他情况下,最好在常见情况下不要求额外的括号,并使用 BOOST_IDENTITY_TYPE
将逗号作为特殊情况处理。例如,对于 BOOST_LOCAL_FUNCTION
就是这种情况,因为始终要求在类型周围使用额外的括号 ((...))
会导致局部函数签名语法不自然。
int BOOST_LOCAL_FUNCTION( ((int&)) x, ((int&)) y ) { // Unnatural syntax. return x + y; } BOOST_LOCAL_FUNCTION_NAME(add)
无需仅在需要时才要求用户指定BOOST_IDENTITY_TYPE
,这允许在参数类型不包含逗号的常见情况下使用更自然的语法BOOST_LOCAL_FUNCTION(int& x, int& y)
(同时仍然允许使用BOOST_LOCAL_FUNCTION(BOOST_IDENTITY_TYPE((std::map<int, char>))& x, int& y)
将包含逗号的参数类型作为特殊情况指定)。
此库宏的实现等效于以下内容:[3]
#include <boost/type_traits/function_traits.hpp> #define BOOST_IDENTITY_TYPE(parenthesized_type) \ boost::function_traits<void parenthesized_type>::arg1_type
本质上,类型被包装在圆括号(std::map<int, char>)
之间,因此即使它包含逗号,也可以将其作为单个宏参数传递。然后,将带括号的类型转换为返回void
的函数的类型,并使用指定的类型作为第一个且唯一的参数void (std::map<int, char>)
的类型。最后,使用function_traits
元函数在编译时提取第一个参数arg1_type
的类型,从而从带括号的类型中获得原始类型(有效地从指定的类型周围剥离额外的括号)。
使用圆括号包装类型表达式,以便即使它们包含逗号也可以传递给宏。
BOOST_IDENTITY_TYPE(parenthesized_type)
BOOST_IDENTITY_TYPE — 此宏允许将指定的类型表达式包装在额外的圆括号中,以便即使类型包含逗号(未在圆括号内包装),也可以将其作为单个宏参数传递。
// In header: <boost/utility/identity_type.hpp>
BOOST_IDENTITY_TYPE(parenthesized_type)
[1] 使用可变参数宏,可以只需要一组额外的括号BOOST_IDENTITY_TYPE(
type
)
,而不是两组BOOST_IDENTITY_TYPE((
type
))
,但是可变参数宏不是 C++03 的一部分(即使现在大多数现代编译器都支持它们,它们也是 C++11 的一部分)。
[2] 这是因为BOOST_IDENTITY_TYPE
的实现将指定的类型包装在元函数中。
[3] 绝对不能保证宏实际上是使用此文档中列出的代码实现的。列出的代码仅用于解释目的。