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. См.
Указатель и Динамическая память.