Неявные преобразования и static_cast
Сумма 3 и -5 равна -2, но эта программа печатает совсем другое число:
#include <iostream>
int main() {
int change = -5;
unsigned stock = 3u;
std::cout << stock + change << '\n'; // 4294967294
}Перед сложением компилятор (compiler) привёл оба операнда (operands) к одному типу, и этим типом оказался unsigned. Отрицательных чисел в нём нет, поэтому и сумма получилась неотрицательной.
Неявные преобразования
Неявное преобразование (implicit conversion) — смена типа, которую компилятор выполняет сам, без указания в коде. Перед арифметикой и сравнением он приводит операнды к общему типу по двум правилам:
bool,charиshortпродвигаются (promotion) доint:true + true— этоintсо значением2.- Если типы всё ещё разные, оба операнда приводятся к общему типу. Для
intиdoubleэтоdouble, дляintиlong long—long long, дляintиunsigned—unsigned.
Вместе эти правила называют обычными арифметическими преобразованиями (usual arithmetic conversions). Отрицательное значение int при переходе в unsigned уходит по кругу, и результат определён стандартом. -1 становится наибольшим значением unsigned, а -5 — числом на четыре меньше. Так же работает сравнение: < с операндами int и unsigned сравнивает уже преобразованные значения.
Обратное направление опаснее. Значение double, записанное в int, теряет дробную часть, а long long может не поместиться в int. Это сужающие преобразования (narrowing): при = они компилируются без ошибки, а фигурные скобки, как показано в теме о переменных, их запрещают.
Приведение static_cast
Когда нужное преобразование компилятор сам не сделает, тип меняют явно. Такая запись называется приведением (cast): static_cast<double>(sum) даёт значение sum в виде double, а сама переменная остаётся int.
#include <iostream>
int main() {
int sum = 7;
int count = 2;
std::cout << static_cast<double>(sum) / count << '\n'; // 3.5
}Делимое стало double, поэтому count тоже превращается в double, и деление сохраняет дробную часть.
Тот же приём защищает от переполнения (overflow). Наименьшее и наибольшее значения int хранят константы INT_MIN и INT_MAX из заголовка (header) <climits>:
#include <climits>
#include <iostream>
int main() {
int a = INT_MAX;
int b = 1;
long long sum = static_cast<long long>(a) + b;
std::cout << sum << '\n'; // 2147483648
}В типе int сумма a + b переполнилась бы, а это неопределённое поведение (undefined behavior). После приведения a к long long в long long переходит и b, и сложение выполняется в широком типе.
В языке C тип меняют записью (double)sum. В C++ она тоже работает, но умеет и снять const, и переинтерпретировать указатель (pointer). По коду не видно, что из этого имелось в виду. Именованное приведение вроде static_cast делает одну работу, и поиск по его имени находит каждое место в коде.
Свои преобразования задают и типы стандартной библиотеки: строковый литерал неявно превращается в std::string, а std::string — в std::string_view. Такие правила пишет автор типа, а компилятор применяет их так же неявно.
Частая ошибка
Приведение применили к результату деления, а не к операнду:
#include <iostream>
int main() {
int sum = 7;
int count = 2;
double avg = static_cast<double>(sum / count); // ✗
std::cout << avg << '\n'; // 3
}Программа печатает 3. В скобках стоит целочисленное деление 7 / 2, и в double превращается уже его результат 3. Приводите операнд: static_cast<double>(sum) / count даёт 3.5.