Заголовок <boost/static_assert.hpp>
содержит два макроса:
BOOST_STATIC_ASSERT(x)
BOOST_STATIC_ASSERT_MSG(x, msg)
Оба генерируют сообщение об ошибке компиляции времени, если интегральное постоянное выражение x
не соответствует действительности. Другими словами, они являются эквивалентом времени компиляции макроса утверждения; это иногда известно как «утверждение времени компиля», но будет называться «статическим утверждением» во всех этих документах. Обратите внимание, что если условие истинно
, то макросы не будут генерировать ни код, ни данные - и макросы также могут использоваться в любом пространстве имен, классе или области функций. При использовании в шаблоне статическое утверждение будет оцениваться в момент создания шаблона; это особенно полезно для проверки параметров шаблона.
Если доступна функция 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);
}
Использование пространства имен my_conditions здесь требует некоторых комментариев. Макро BOOST_STATIC_ASSERT
работает, генерируя объявление typedef, и поскольку typedef должен иметь имя, макрос автоматически генерирует его, искажая имя stub со значением __LINE__
. Когда BOOST_STATIC_ASSERT
используется в любом классе или функциональном диапазоне, то каждое использование BOOST_STATIC_ASSERT
гарантирует создание уникального для этого диапазона имени (при условии, что вы используете макрос только один раз на каждой строке). Однако при использовании в заголовке в области пространства имен это пространство имен может быть продолжено над несколькими заголовками, каждый из которых может иметь свои собственные статические утверждения, и на «одинаковых» линиях, тем самым генерируя дублирующие декларации. Теоретически компилятор должен молча игнорировать повторяющиеся объявления типа DEF, однако многие не делают этого (и даже если они это делают, они имеют право выдавать предупреждения в таких случаях). Чтобы избежать потенциальных проблем, если вы используете BOOST_STATIC_ASSERT
в заголовке и в области пространства имен, затем закройте их в пространстве имен, уникальном для этого заголовка.
Макрос обычно используется в области функций внутри функций шаблона, когда аргументы шаблона нуждаются в проверке. Представьте, что у нас есть алгоритм на основе итератора, который требует итераторов случайного доступа. Если алгоритм инстанцирован с итераторами, которые не соответствуют нашим требованиям, то в конечном итоге будет сгенерирована ошибка, но она может быть вложена глубоко внутри нескольких шаблонов, что затрудняет пользователю определение того, что пошло не так. Один из вариантов заключается в добавлении статического утверждения на верхнем уровне шаблона, в том случае, если условие не выполнено, то ошибка будет генерироваться таким образом, чтобы пользователю было достаточно очевидно, что шаблон используется неправильно.
#include <iterator>
#include <boost/static_assert.hpp>
#include <boost/type_traits.hpp>
template <class RandomAccessIterator >
RandomAccessIterator foo(RandomAccessIterator from,
RandomAccessIterator to)
{
typedef typename std::iterator_traits<
RandomAccessIterator >::iterator_category cat;
BOOST_STATIC_ASSERT(
(boost::is_convertible<
cat,
const std::random_access_iterator_tag&>::value));
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:
};
Обычно статические утверждения при использовании внутри шаблона класса или функции не будут инстанцированы до тех пор, пока шаблон, в котором они используются, не будет инстанцирован. Однако есть одна потенциальная проблема, на которую следует обратить внимание: если статическое утверждение не зависит от одного или нескольких параметров шаблона, то компилятору разрешается оценивать статическое утверждение в точке, в которой оно впервые видно, независимо от того, когда шаблон когда-либо создан, например:
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
{
BOOST_STATIC_ASSERT(sizeof(T) == 0);
};