Boost C++ 库

...世界上最受推崇和设计最精良的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ 编码标准

Boost.Utility/IdentityType 1.0.0

Lorenzo Caminiti

根据 Boost 软件许可协议 1.0 版发布(请参阅随附文件 LICENSE_1_0.txt 或 https://boost.ac.cn/LICENSE_1_0.txt 的副本)

目录

动机
解决方案
模板
抽象类型
附录:用法
附录:实现
参考

此库允许将类型包装在圆括号内,以便它们始终可以作为宏参数传递。

考虑以下宏,它声明一个名为 varn 的变量,其类型为指定的 type(另请参阅 var_error.cpp

#define VAR(type, n) type var ## n

VAR(int, 1);                    // OK.
VAR(std::map<int, char>, 2);    // Error.

第一个宏调用正确地声明了一个名为 var1int 类型的变量。但是,第二个宏调用失败,生成类似于以下的预处理器错误

error: macro "VAR" passed 3 arguments, but takes just 2

这是因为作为第一个宏参数传递的 std::map 类型包含一个逗号 ,,该逗号未被圆括号 () 包裹。预处理器将未包裹的逗号解释为宏参数之间的分隔符,从而得出结论:总共向宏传递了三个(而不是两个)参数,顺序如下

  1. std::map<int
  2. char>
  3. 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 为前缀。例如,让我们编写一个宏,它声明一个名为 argn 的函数参数,其类型为指定的 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 宏可用于将具有多个模板参数的元函数传递给断言宏(以便处理分隔模板参数的逗号)。在这种情况下,如果元函数是抽象类型,则需要通过添加和删除对其的引用来对其进行操作(另请参阅 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

BOOST_IDENTITY_TYPE — 此宏允许将指定的类型表达式包裹在额外的圆括号内,以便即使类型包含逗号(尚未包裹在圆括号内),也可以作为单个宏参数传递。

概要

// In header: <boost/utility/identity_type.hpp>

BOOST_IDENTITY_TYPE(parenthesized_type)

描述

参数

parenthesized_type要作为宏参数传递的类型表达式,用一组圆括号 (...) 包裹。此类型表达式可以包含任意数量的逗号。

此宏适用于任何 C++03 编译器(它不使用可变参数宏)。

当在模板中使用此宏时,必须以 typename 为前缀。请注意,当函数模板参数用此宏包裹时,编译器将无法自动确定它们(在调用函数模板时需要显式指定这些参数)。

在某些编译器(如 GCC)上,对抽象类型使用此宏需要添加和删除对指定类型的引用。



[1] 使用可变参数宏,可以要求使用一组额外的括号 BOOST_IDENTITY_TYPE(type) 而不是两组 BOOST_IDENTITY_TYPE((type)),但可变参数宏不是 C++03 的一部分(即使如今它们已被大多数现代编译器支持,并且也是 C++11 的一部分)。

[2] 这是因为 BOOST_IDENTITY_TYPE 的实现将指定的类型包裹在元函数中。

[3] 绝对不能保证宏实际上是使用本文档中列出的代码实现的。列出的代码仅用于解释目的。