版权所有 © 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)
如果 integral-constant-expression x
不为真,两者都会生成编译时错误消息。换句话说,它们是 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; }
这里有几个脚注需要说明:assert 周围的额外括号是为了防止预处理器将 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); };