Семь лет назад автор выпустил пост о конструкциях C, которые не работают в C++. В 2026 он обновил его — границы сдвинулись. Главный вывод: теперь недостаточно сказать «валидный C» или «валидный C++». Надо уточнять режим языка — C17, C23, C++17, C++20 или C++23.
Дизайнированные инициализаторы из C99 в C++20 наконец появились, но не в полном объёме. В C++ они работают только для прямых нестатических членов, строго в порядке объявления, и нельзя мешать их с позиционными. То есть .retries = 3, .timeout_ms = 5000 — уже ошибка, хотя в C норм. Массивные и вложенные дизайнаторы вроде [2] = 99 или .inner.value = 7 в C++ до сих пор не завезли.
Пустые списки параметров — тут C23 наоборот подтянулся к C++. Раньше void fn() в C не задавал прототип, и можно было вызвать fn(42) с неопределённым поведением. Теперь C23 ведёт себя как C++: fn() эквивалентно fn(void). Для общих заголовков всё равно безопаснее писать void fn(void) — старый C может сломаться.
С void* и malloc ничего кардинально не поменялось — C++ не умеет неявно кастовать void* к указателю на объект. Но C++20 подлатал время жизни: для типов с неявным временем жизни (тривиальные структуры) malloc теперь корректно создаёт объекты. Но конструкторы всё равно не вызываются, а для std::string такой трюк остаётся неопределённым поведением. Каст — не главная проблема, главная — кто владеет памятью, когда стартует жизнь объекта и кто его убивает.
const_cast — компиляция не означает корректность. Если оригинальный объект был объявлен const, писать через каст нельзя — UB. Только когда исходный объект неконстантный, можно временно снять const ради старой C-функции, которая обещает не писать.
Перечисления: C23 ввёл фиксированные базовые типы, но модель всё ещё отличается от C++. В C++ enum — отдельный тип, int в него сам не сконвертируется, а enum class вообще не конвертится в int. Для C++-API лучше использовать enum class, plain enums — только ради ABI с C.
restrict в стандартном C++ нет — только расширения компиляторов вроде __restrict__ у GCC и Clang или __restrict у MSVC. Их стоит прятать за портабельные границы.
Flexible array members (пустой массив в конце структуры) — стандартный C, но не C++. На границе ABI можно сохранить C-разметку, но внутри C++ лучше переводить в std::vector или std::span.
Итог: C и C++ всё больше делят синтаксис, но не модель объектов, инициализации и инвариантов. Метка языка в обсуждениях — больше не роскошь, а необходимость.