版权所有 © 2000, 2005 Steve Cleary 和 John Maddock
根据 Boost 软件许可证,版本 1.0 分发。(参见随附文件 LICENSE_1_0.txt 或复制于 https://boost.ac.cn/LICENSE_1_0.txt )
本手册也可提供 适合打印的 PDF 格式。
头文件 <boost/static_assert.hpp>
提供了两个宏
BOOST_STATIC_ASSERT(x) BOOST_STATIC_ASSERT_MSG(x, msg)
如果整型常量表达式 x
不为 true,这两个宏都会生成编译时错误消息。换句话说,它们是 assert 宏的编译时等效项;这有时被称为“编译时断言”,但在本文档中将被称为“静态断言”。请注意,如果条件为 true
,则宏既不会生成代码也不会生成数据 - 并且宏也可以在命名空间、类或函数作用域使用。在模板中使用时,静态断言将在模板实例化时进行评估;这对于验证模板参数特别有用。
如果 C++0x 的 static_assert
功能可用,则两个宏都将使用它。对于 BOOST_STATIC_ASSERT(x)
,错误消息将是 x
的字符串化版本。对于 BOOST_STATIC_ASSERT_MSG(x, msg)
,错误消息将是 msg
字符串。
如果 C++0x 的 static_assert
功能不可用,BOOST_STATIC_ASSERT_MSG(x, msg)
将被视为 BOOST_STATIC_ASSERT(x)
。
以下内容假设 C++0x 的 static_assert
功能不可用。
BOOST_STATIC_ASSERT
的目标之一是生成可读的错误消息。这些消息会立即告诉用户库的使用方式不受支持。虽然错误消息显然因编译器而异,但您应该会看到类似以下内容
Illegal use of STATIC_ASSERTION_FAILURE<false>
这是为了至少引起注意!
您可以在可以放置声明的任何位置使用 BOOST_STATIC_ASSERT
,即在类、函数或命名空间作用域,以下示例说明了这一点
如果某些要求必须始终为真,则可以在命名空间作用域使用该宏;通常这意味着某些平台特定要求。假设我们要求 int
至少是一个 32 位整型,并且 wchar_t
是无符号类型。我们可以在编译时验证这一点,如下所示
#include <climits> #include <cwchar> #include <limits> #include <boost/static_assert.hpp> namespace my_conditions { BOOST_STATIC_ASSERT(std::numeric_limits<int>::digits >= 32); BOOST_STATIC_ASSERT(WCHAR_MIN >= 0); } // namespace my_conditions
这里使用命名空间 my_conditions 需要一些注释。宏 BOOST_STATIC_ASSERT
通过生成 typedef 声明来工作,并且由于 typedef 必须有一个名称,因此宏通过将存根名称与 __LINE__
的值进行混合来自动生成一个名称。当 BOOST_STATIC_ASSERT
在类或函数作用域使用时,保证每次使用 BOOST_STATIC_ASSERT
都会生成该作用域唯一的名称(前提是您在每一行上只使用一次该宏)。但是,当在头文件中以命名空间作用域使用时,该命名空间可以延续到多个头文件,每个头文件可能都有自己的静态断言,并且在“相同”的行上,从而生成重复的声明。理论上,编译器应该默默地忽略重复的 typedef 声明,但是很多编译器不会这样做(即使它们这样做,它们也有权在这种情况下发出警告)。为了避免潜在的问题,如果您在头文件中以命名空间作用域使用 BOOST_STATIC_ASSERT
,则将它们括在该头文件唯一的命名空间中。
该宏通常在模板函数内部的函数作用域使用,当需要检查模板参数时。假设我们有一个需要随机访问迭代器的基于迭代器的算法。如果使用不满足我们要求的迭代器实例化算法,则最终会生成错误,但这可能会嵌套在多个模板深处,使用户很难确定出了什么问题。一种选择是在模板的顶层添加静态断言,在这种情况下,如果不满足条件,则会以一种对用户来说相当明显模板被误用的方式生成错误。
#include <iterator> #include <boost/static_assert.hpp> #include <boost/type_traits.hpp> template <class RandomAccessIterator > RandomAccessIterator foo(RandomAccessIterator from, RandomAccessIterator to) { // this template can only be used with // random access iterators... typedef typename std::iterator_traits< RandomAccessIterator >::iterator_category cat; BOOST_STATIC_ASSERT( (boost::is_convertible< cat, const std::random_access_iterator_tag&>::value)); // // detail goes here... return from; }
这里需要说明几点:断言周围的额外括号是为了防止预处理器将 is_convertible
模板内的逗号解释为宏参数分隔符;is_convertible
的目标类型是引用类型,因为某些编译器在通过用户定义的构造函数进行转换时使用 is_convertible
会出现问题(无论如何,不能保证迭代器标签类是可复制构造的)。
该宏通常在作为模板的类内部使用。假设我们有一个模板类,它需要一个至少 16 位精度的无符号整型作为模板参数,我们可以使用如下代码来实现
#include <limits> #include <boost/static_assert.hpp> template <class UnsignedInt> class myclass { private: BOOST_STATIC_ASSERT_MSG(std::numeric_limits<UnsignedInt>::is_specialized, "myclass can only be specialized for types with numeric_limits support."); BOOST_STATIC_ASSERT_MSG(std::numeric_limits<UnsignedInt>::digits >= 16, "Template argument UnsignedInt must have at least 16 bits precision.") BOOST_STATIC_ASSERT_MSG(std::numeric_limits<UnsignedInt>::is_integer, "Template argument UnsignedInt must be an integer."); BOOST_STATIC_ASSERT_MSG(!std::numeric_limits<UnsignedInt>::is_signed, "Template argument UnsignedInt must not be signed."); public: /* details here */ };
通常,在类或函数模板内部使用的静态断言,直到使用它的模板被实例化时才会被实例化。但是,有一个潜在的问题需要注意:如果静态断言不依赖于一个或多个模板参数,则允许编译器在第一次看到它时就对其进行评估,而不管模板是否被实例化,例如
template <class T> struct must_not_be_instantiated { BOOST_STATIC_ASSERT(false); };
某些编译器(例如 Intel 8.1 或 gcc 3.4)会产生编译器错误,而不管模板是否被实例化。在这种情况下,一种解决方法是强制断言依赖于模板参数
template <class T> struct must_not_be_instantiated { // this will be triggered if this type is instantiated BOOST_STATIC_ASSERT(sizeof(T) == 0); };