Что делает static_assert и для чего вы его используете?

Не могли бы вы привести пример, где static_assert(...) ('С++ 11') изящно решит проблему?

Я знаком со временем выполнения assert(...). Когда мне следует отдавать предпочтение static_assert(...), а не обычному assert(...)?

Кроме того, в boost есть что-то под названием BOOST_STATIC_ASSERT, это то же самое, что и static_assert(...)?

Ответ 1

Сверху моей головы...

#include "SomeLibrary.h"

static_assert(SomeLibrary::Version > 2, 
         "Old versions of SomeLibrary are missing the foo functionality.  Cannot proceed!");

class UsingSomeLibrary {
   // ...
};

Предполагая, что SomeLibrary::Version объявляется как статический const, а не #define d (как и следовало ожидать в библиотеке С++).

Контраст с необходимостью компилировать SomeLibrary и ваш код, связать все и запустить исполняемый файл только тогда, чтобы узнать, что вы потратили 30 минут на компиляцию несовместимой версии SomeLibrary.

@Arak, в ответ на ваш комментарий: да, вы можете static_assert просто сидеть где угодно, от внешнего вида:

class Foo
{
    public: 
        static const int bar = 3;
};

static_assert(Foo::bar > 4, "Foo::bar is too small :(");

int main()
{ 
    return Foo::bar;
}
$ g++ --std=c++0x a.cpp
a.cpp:7: error: static assertion failed: "Foo::bar is too small :("

Ответ 2

Статическое утверждение используется для выполнения утверждений во время компиляции. Когда статическое утверждение терпит неудачу, программа просто не компилируется. Это полезно в разных ситуациях, например, если вы реализуете некоторые функции с помощью кода, который критически зависит от объекта unsigned int, имеющего ровно 32 бита. Вы можете поставить статическое утверждение вроде этого

static_assert(sizeof(unsigned int) * CHAR_BIT == 32);

в вашем коде. На другой платформе с типом unsigned int с разным размером компиляция завершится неудачей, обратив внимание разработчика на проблемную часть кода и посоветовав им повторно реализовать или повторно проверить его.

В другом примере вам может потребоваться передать некоторое целое значение в качестве указателя void * к функции (взломать, но полезно время от времени), и вы хотите убедиться, что интегральное значение будет вписываться в указатель

int i;

static_assert(sizeof(void *) >= sizeof i);
foo((void *) i);

Вам может потребоваться активация типа char

static_assert(CHAR_MIN < 0);

или интегральное деление с отрицательными значениями округляется к нулю

static_assert(-5 / 2 == -2);

И так далее.

Во многих случаях могут использоваться утверждения времени выполнения вместо статических утверждений, но утверждения во время выполнения работают только во время выполнения и только тогда, когда управление передает это утверждение. По этой причине неудачное утверждение во время выполнения может оставаться бездействующим, необнаруженным в течение длительных периодов времени.

Конечно, выражение в статическом утверждении должно быть константой времени компиляции. Это не может быть значением времени выполнения. Для значений времени выполнения у вас нет другого выбора, кроме обычного assert.

Ответ 3

Я использую его, чтобы убедиться, что мои предположения о поведении компилятора, заголовках, библиотеках и даже моем собственном коде верны. Например, здесь я проверяю, что структура была правильно упакована до ожидаемого размера.

struct LogicalBlockAddress
{
#pragma pack(push, 1)
    Uint32 logicalBlockNumber;
    Uint16 partitionReferenceNumber;
#pragma pack(pop)
};
BOOST_STATIC_ASSERT(sizeof(LogicalBlockAddress) == 6);

В обертке класса stdio.h fseek() я взял несколько ярлыков с enum Origin и убедитесь, что эти ярлыки совпадают с константами, определяемыми stdio.h

uint64_t BasicFile::seek(int64_t offset, enum Origin origin)
{
    BOOST_STATIC_ASSERT(SEEK_SET == Origin::SET);

Вы должны предпочесть static_assert над assert, когда поведение определено во время компиляции, а не во время выполнения, например, приведенные выше примеры. Пример, когда это не так, будет включать проверку кода и кода возврата.

BOOST_STATIC_ASSERT - это макрос pre-С++ 0x, который генерирует незаконный код, если условие не выполняется. Намерения одинаковы, хотя static_assert стандартизирован и может обеспечить лучшую диагностику компилятора.

Ответ 4

BOOST_STATIC_ASSERT - это кросс-платформенная оболочка для static_assert.

В настоящее время я использую static_assert для принудительного применения "Концепций" в классе.

Пример:

template <typename T, typename U>
struct Type
{
  BOOST_STATIC_ASSERT(boost::is_base_of<T, Interface>::value);
  BOOST_STATIC_ASSERT(std::numeric_limits<U>::is_integer);
  /* ... more code ... */
};

Это приведет к ошибке времени компиляции, если какое-либо из вышеперечисленных условий не будет выполнено.

Ответ 5

Одно использование static_assert может заключаться в том, чтобы структура (то есть интерфейс с внешним миром, такой как сеть или файл) была точно такой, какой вы ожидаете. Это будет ловить случаи, когда кто-то добавляет или изменяет члена из структуры, не осознавая последствий. static_assert подберет его и предупредит пользователя.

Ответ 6

В отсутствие понятий можно использовать static_assert для простой и читаемой проверки типа компиляции, например, в шаблонах:

template <class T>
void MyFunc(T value)
{
static_assert(std::is_base_of<MyBase, T>::value, 
              "T must be derived from MyBase");

// ...
}

Ответ 7

Это напрямую не отвечает на исходный вопрос, но делает интересное исследование того, как применять эти проверки времени компиляции до С++ 11.

Глава 2 (Раздел 2.1) "Современный дизайн С++" от Andrei Alexanderscu реализует эту идею утверждений времени компиляции, таких как

template<int> struct CompileTimeError;
template<> struct CompileTimeError<true> {};

#define STATIC_CHECK(expr, msg) \
{ CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; } 

Сравните макрос STATIC_CHECK() и static_assert()

STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");

Ответ 8

static_assert можно использовать, чтобы запретить использование ключевого слова delete следующим образом:

#define delete static_assert(0, "The keyword \"delete\" is forbidden.");

Каждый современный разработчик C++ может захотеть сделать это, если он или она хочет использовать консервативный сборщик мусора, используя только класс es и struct, которые перегружают оператор new чтобы вызвать функцию, которая выделяет память в консервативной куче консервативного сборщика мусора, который можно инициализировать и создать, вызвав некоторую функцию, которая делает это в начале функции main.

Например, каждый современный разработчик C++, который хочет использовать консервативный сборщик мусора Boehm-Demers-Weiser, в начале функции main напишет:

GC_init();

И в каждой class и struct перегрузите operator new следующим образом:

void* operator new(size_t size)
{
     return GC_malloc(size);
}

И теперь, когда operator delete больше не нужен, так как консервативный сборщик мусора Boehm-Demers-Weiser отвечает как за освобождение, так и за освобождение каждого блока памяти, когда он больше не нужен, разработчик хочет запретить delete ключевое слово.

Один из способов - перегрузить delete operator таким образом:

void operator delete(void* ptr)
{
    assert(0);
}

Но это не рекомендуется, потому что современный разработчик C++ будет знать, что он/она по ошибке вызвал delete operator во время выполнения, но лучше знать это вскоре во время компиляции.

Поэтому, на мой взгляд, лучшее решение этого сценария - использовать static_assert, как показано в начале этого ответа.

Конечно, это также может быть сделано с BOOST_STATIC_ASSERT, но я думаю, что static_assert лучше и его следует предпочитать всегда.