static_cast<T>(value) — явное преобразование значения к типу T, проверяемое на этапе компиляции. Компилятор откажется, если преобразование бессмысленно.

double value = 2.9;
int truncated = static_cast<int>(value);   // 2

Зачем писать явно

Многие преобразования компилятор сделает и сам, молча. Проблема в том, что часть из них теряет данные, и явная запись — способ сказать «я знаю, что делаю»:

int total = 7;
int count = 2;
 
double wrong = total / count;                      // 3.0: делили целые
double right = static_cast<double>(total) / count; // 3.5

Во второй строке приведение меняет тип до деления, поэтому работает вещественная арифметика. Это самое частое применение static_cast в учебных задачах — см. Целочисленное деление и остаток.

Второй частый сюжет — промежуточное переполнение. Тип переменной слева вычисление не расширяет, расширять надо операнд:

int x = 2'000'000'000;
int y = 2'000'000'000;
 
long long bad = x + y;                             // UB: сложение идёт в int
long long ok  = static_cast<long long>(x) + y;     // 4000000000

Один операнд становится long long, второй приводится к нему перед сложением, и результат помещается. См. Арифметические преобразования типов.

Третье применение — заставить поток напечатать char как число:

char letter = 'v';
std::cout << static_cast<int>(letter) << '\n';   // 118

См. Символьный тип char.

Почему не в стиле C

Старая запись (int)value называется C-style cast и запрещена кодстайлом курса. Причина не в эстетике: C-style cast последовательно пробует несколько разных преобразований, включая const_cast и reinterpret_cast, и молча применяет первое подходящее. То есть одна и та же запись может оказаться безобидным приведением числа, а может — снятием const или переинтерпретацией памяти, и по коду не видно, что именно произошло.

static_cast умеет только безопасную часть и ломается на этапе компиляции, если попросить лишнего. Плюс его легко найти поиском по коду, а (int) — нет. Ещё clang-tidy ловит C-style cast проверкой google-readability-casting.

Что происходит при сужении

  • double → int: дробная часть отбрасывается, усечение к нулю (static_cast<int>(-2.9) даёт −2). Округление — std::round из <cmath>.
  • Большое значение → более узкий целый тип: старшие биты теряются, результат для знаковых типов до C++20 был определён реализацией.
  • double → int, когда значение после отбрасывания дробной части не влезает в диапазон int: Неопределённое поведение.

Явное преобразование не гарантирует сохранение информации. static_cast говорит «я знаю, что делаю», а не «данные не потеряются».

Другие приведения

static_cast — один из четырёх именованных: const_cast снимает const, reinterpret_cast переинтерпретирует биты, dynamic_cast проверяет тип в иерархии классов во время выполнения. Три последних в начале курса не нужны; появление reinterpret_cast в учебном коде почти всегда означает, что что-то пошло не так.

Частые ошибки

  • static_cast<double>(a / b) вместо static_cast<double>(a) / b: приведение уже после целочисленного деления ничего не спасает.
  • Ожидание округления вместо усечения.
  • C-style cast по привычке из С — не пройдёт проверку clang-tidy.

Углубление — L03

static_cast возвращает тип указателю void*. Так работают с результатом malloc и operator new, которые возвращают «просто адрес»:

int* p = static_cast<int*>(std::malloc(sizeof(int)));
*p = 0;
std::free(p);

Определение в L03: static_cast принудительно выполняет допустимые неявные преобразования значений одного типа в другой. Примеры с делением и переполнением там те же, что выше, только с float и int64_t. См. Указатель и Динамическая память.