«неявные конверсии это зло»
безопасные преобразования: integer promotion, floating point promotion, bool -> integer, int -> float/double сумма инта и unsigned инта это unsigned инт переполнение int это UB, переполнение unsigned int это вычисление по модулю (обнуление при переполнении)
int x = 0-1u это 4млрд c++14 можно разделять разряды 1’000’000
часть видов инициализаций:
- int x = 2; - копирующая инициализация / value initialization если типы не совпадают, выполняется последовательность неявных преобразований
- int x(2); - прямая инициализация / direct_initialization для фундаментальных типов аналогична копирующей, для своих - могут вызываться только explicit конверсии, если типы не совпадают
- int x{2}; - универсальная / фигурная инициализация / uniform initialization предотвращает неявные сужающие преобразования, если они не безопасны (если то, что внутри {} выходит за пределы типа). пустые {} - инициализация 0
касты:
- static_cast - явное преобразование
- reinterpret_cast - “считай эти биты как другой тип”, то есть без перекодировки. часто приводит к нарушению strict aliasing - когда объект читается через тип, несовместимый с его оригинальным типом, что UB
- const_cast - если дана константная ссылка, но если на самом деле под ней не константа, то можно через консткаст её изменить. если же под ней константа, то всё скомпилируется, но будет UB. по кодстайлу запрещён const_cast может навешивать/убирать volatile так же, как и const
- std::bit_cast (C++20) - делает memcpy из одного типа в другой. размеры типов должны совпадать и типы должны быть trivially copyable. не нарушает strict aliasing, так как создаёт новый объект, а не просто переинтерпретирует память.
обращение по указателю через reinterpret_cast на несоответствующий тип - UB
но есть разрешения
без оптимизации выведет 11, с оптимизацией (O2) - 22, / -O2 -fno-strict-aliasing (будет 11)
потому что правило алиасинга, что нельзя через long long* и int* обращаться к одному и тому же - под ними не может лежать один и тот же объект. компилятор предполагает, что то, что лежит под a и b никак не связано, поэтому когда мы один раз прочитали, что лежит под a, мы второй раз не читаем, что лежит под a и поэтому возвращаем что запомнили
если заменить int на char, то UB не будет а ещё можно оставить int, но написать volatile перед long long (будет 11) volatile - квалификатор типа, синтаксически ведет себя как const, неявный каст к нему разрешен, обратно - нет запрещает компилятору оптимизировать обращения к этой перменной заставляет компилятор всегда проверять, чему равна эта переменная, даже если ему кажется, что она не могла поменяться между двумя её последовательными чтениями здесь костыль, чтоб починить UB. нужно просто strict aliasing не нарушать с C++20 не рекомендуется так писать однако в микроконтроллерах часто используется, где переменные изменяются из-вне
правило алиасинга типов (strict aliasing):
aliasing - когда можем обращаться к одному типу через glvalue (ptr/ref) на другой (псевдонимизация типов) другой тип может быть только
- динамическим типом этого же объекта (мб +cv)
void* p = malloc(sizeof(int));int* ip = new (p) int{0};` - знаковым/беззнаковым типом соответствующего дин типу этого объекта
в стандарте для char/unsigned char (и std::byte) сделано исключение - через указатель на char можно обращаться к любому другому типу
std::bit_cast<T>() - делает memcpy полезна на случай, если мы хотим смотреть на байты, будто бы это другой тип, но через reinterpret_cast к указателю на другой тип нарушится strict aliasing

касты это экспрешены
c-style cast работает так:
- пытается сделать const_cast
- если CE, то пытается сделать static_cast
- если CE, то пытается сделать const_cast + static_cast
- если CE, то пытается сделать reinterpret_cast
- если CE, то пытается const_cast + reinterpret_cast
- если CE, то CE
нельзя привести интовую переменную к long long& потому что нужно будет сконвертировать инт к лонг лонг и получится rvalue
static_cast может только к compile time типам делать
void f(…) осталось в C++ даже при том, что template<typename… T> есть для того, чтоб старый printf работал и чтоб оно получало низший приоритет перегрузки, а typename… T имеет точное совпадение
void f(…) имеет низший приоритет, потому что это дорого в ассемблере