C++23, на момент разработки C++2b — новый стандарт языка программирования C++. Из-за эпидемии КОВИДа все заседания комитета были полностью дистанционные (кроме одного, частично дистанционного).
 На июль 2023 закончен, но не прошёл через ISO. Макрос __cplusplus поднят до 202302L.
 Более раннее std::is_constant_evaluated(), сделанное встроенной функцией компилятора, оказалось ошибкоопасным. Например:
 Конечно, компиляторы выдают предупреждение, но неочевидно, что делать — правильно if (std::is_constant_evaluated()), иначе оптимизированная ассемблерная версия вообще не запустится.
 Вторая причина — взаимодействие между constexpr и consteval.
 Этот код вообще не компилировался — consteval-функцию отсюда вызывать нельзя.
 Фигурные скобки в then-части обязательны, в else- могут опускаться. Писать вроде if (consteval && n < 0) { невозможно, ради этого старую функцию не запретили.
 Обратная форма выглядит как if not consteval {} или if !consteval {}.
 Простой способ получить объект как временный, например:
 x.front() — ошибка: в зависимости от контейнера, эта ссылка будет смотреть или на другой объект, или в пустую память.
 Нижеприведённый код корректен, но ревизор может соблазниться ошибочно убрать переменную a.
 В шаблонном программировании этот тип бывает получить непросто:
 Название prvalue_cast было отброшено по двум причинам: prvalue — сильно техническое понятие, и не соответствующее названию поведение для массивов (даст указатель).
 Также допустимо auto{x}.
 Существующие методы:
 Пока только для пользовательских типов.
 Разные библиотеки реализуют недостающий синтаксис по-разному, но в любом случае это не сочетается с синтаксисом стандартных массивов, и затрудняет автоматический поиск ошибок и inline (развёртывание функции прямо в вызывающий код).
 Предметом дискуссий остаются: нужно ли это для стандартных массивов; нужно ли ослабить требования к operator и разрешить его за пределами класса.
 Одна из возможностей C++ — const-корректность — приводит к дублированию кода или написанию способов делегирования. Предлагается решение этого через шаблоны
 Методы-расширения пока не предлагаются, но будут возможны в дальнейшем.
 Сделано множество послаблений: сигнатура не обязательно состоит из литеральных типов, подобъекты не обязательно конструируются/уничтожаются через constexpr, и т. д. Аргументация:
 Таким образом, теперь возможно написать constexpr-функцию, которая ни при одном наборе аргументов не сможет выполниться при компиляции. Должен ли компилятор в таком случае выдавать предупреждения — не указано.
 Также в constexpr-функциях разрешены goto, переменные нелитеральных типов, статические/внутрипоточные переменные. Если при компиляции будет пройдена любая из этих строк, функция вычисляется при выполнении.
 В constexpr-функциях теперь могут участвовать указатели и ссылки с неизвестным значением, если этими значениями не пользоваться — оказалось, что std::size не может работать со ссылками:
 Разрешены static constexpr-переменные в constexpr-функциях:
 Это позволит, например, написать constexpr from_chars.
 Лямбда-функции и constexpr-шаблоны могут вызывать consteval и тогда сами становятся consteval. Для первой никак нельзя указать, что она consteval. А шаблон теперь может стать условным consteval в зависимости от пути инстанцирования.
 Убирает одну машинную команду, если класс без данных и inline не получился. Например, в самобалансирующемся дереве с нестандартным порядком (было в Си++03) и разнородным поиском (C++14) возможен такой код:
 Изначальное предложение касалось операции «вызов» operator(). Потом позволили делать статической и операцию «индекс» operator.
 Эти операции всё ещё нельзя определять вне класса.
 Разрешено аннотировать только пустой оператор. Код в аннотации никогда не исполняется, даже если имеет побочные эффекты. Служит исключительно для оптимизатора — он может закладываться на данное выражение. Если выражение будет равняться false то это неопределённое поведение. Функция __assume/__builtin_assume уже есть в MSVC и Clang, а вот G++ эмулирует её через builtin_unreachable и потому вычисляет выражение внутри.
 В данном примере, если x неотрицательный, можно делать лёгкую команду shr (беззнаковый сдвиг) или sar (знаковый — такой сдвиг равноценен делению с округлением вниз, в то время как операция / округляет к нулю). Если закладываться на отрицательный — то тяжёлую команду div (деление) или нетривиальные оптимизации.
 Если при constexpr-счёте окажется, что assume не выполняется — поведение остаётся за компилятором: он может как выдать ошибку, так и ничего не сделать. Это не первая вещь, где поведение в constexpr за компилятором — также за компилятором будет распаковка переменных параметров Си (на манер функции printf) и расчёты с неопределённым поведением.
 Чтобы писать меньше нетворческого кода, в С++20 сделали синтез операции «не равняется» из «равняется», и примерку обеих как в обычном виде, так и в перевёрнутом. Это сильно ударило по имевшемуся коду: на G++ работает с предупреждением, например, такой рекурсивный шаблон — выбирает между простой и перевёрнутой операцией ==:
 Теперь синтез операции «не равняется» и переворот происходит, если возвращаемый тип bool и программист не написал операцию != сам. В некоторых случаях компилятор может запутаться и сказать: есть выбор между обычной и перевёрнутой операцией «равняется», и исправление этой ошибки простое — поступить по старинке и написать операцию «не равняется» самостоятельно.
 У нового перечисления вариантов через if constexpr нет одной черты, присутствовавшей у старых способов обеспечить принципиально разное поведение (например, у перегрузок ostream << int и ostream << const char*): как вызвать ошибку, если ни одна ветвь не выполняется? static_assert(false) не годился — компилятор выдавал ошибку, даже не инстанцируя; приходилось запутывать компилятор так, чтобы он не видел тождественный false. Общепринятое решение — шаблон always_false<T>, который пишут прямо на месте.
 В С++23 static_assert в шаблонах проверяется при инстанцировании, даже если выражение можно вычислить, не инстанцируя. Если он должен проверяться всегда — вытащите его за пределы шаблона.
 В идентификаторах теперь допустимы символы из множеств Юникода XID_Start (начальный) и XID_Continue (остальные).
 Идентификатор должен быть нормализован по алгоритму «каноническая композиция» (NFC, разобрать монолитные символы на компоненты и собрать снова). Если нет — программа некорректна.
 Это изменение только делает поддержку Юникода более целостной, но никак не решает вопросов атак через внешне одинаковые строки. Методы передачи таких символов в линкер остаются за реализацией.
 Разные компиляторы действовали по-разному на L'\U0001F926' (эмодзи «фейспалм») на двухбайтовом wchar_t (Windows), L'ab'. Теперь оба запрещены.
 Многосимвольные char-литералы продолжают работать, имеют тип int. Сколько допускается символов и как они будут собраны в одно число — определяется реализацией.
 Узаконено, что одна может отличаться от другой, и wchar_t — это единица широкой, зависящей от реализации кодировки исполнения.
 UTF-8 как кроссплатформенная кодировка трансляции должна поддерживаться безусловно, всеми компиляторами. Метка порядка байтов игнорируется, кроме случаев, когда она противоречит флагам компилятора. Если файл опознан как UTF-8, в нём не должно быть некорректных кодовых комбинаций — однако могут быть корректные комбинации, соответствующие не существующим пока символам.
 Раньше это было за реализацией, но оказалось, что основное назначение этой функции — определение кодировки исполнения. Например, код из SQLite:
 Все крупные компиляторы фактически работают именно так.
 Все три строчки сломаны в Си++20, снова работают в Си++23.
 Как оказалось, подобный слом усложнял constexpr-функции, мешал совместимости с Си.
 "\u{1F926}" для кодовой позиции Юникода, "\o{123}" для восьмеричной системы и "\x{AB}" для шестнадцатеричной.
 Разрывать такие экранировки ("\x{4" "2}") запрещено.
 "\N{LATIN CAPITAL LETTER A WITH MACRON}" позволяет обратиться к символу по юникодному имени.
 Исключает крокозябры в таком коде:
 Набор возможных кодировок определяется реализацией. При неспособности — выбрасывается исключение.
 Для каждого текста реализация сама определяет, сколько позиций терминала он займёт. Описана эталонная юникодная реализация: большинство восточноазиатских символов и эмодзи имеют ширину 2, все умляуты — 0, у остальных 1. Реализация локаленезависима. Ширина символа-заполнителя всегда принимается за 1.
 Специальные числа вроде NaN и ∞ не заполняются нулями.
 В конструкции «for по объекту» говорится: каждый временный объект, в нормальных условиях исчезающий в конце строки, сохраняется до конца цикла. Исключением являются переданные по значению параметры функции — ведь они, исчезая как объекты в конце строки, вероятно, теряют своё содержимое раньше.
 Это один из тех немногих случаев, когда ради безопасного кода нарушено правило «деструкторы вызываются сразу же после окончания выражения». Прочие случаи: создание/копирование массива, явная команда придержать объект через const int& b = static_cast<const int&>(0);.
 Эти обёртки призваны «подружить» «сырые» Си-API и умные указатели. Исчезая, обёртка перезаписывает указатель. В любом случае в типе умного указателя программист должен верно указать, какой функцией объект уничтожать.
 Одно из важнейших нововведений Си++23, позволяющее видеть ошибку вернее, чем короткие сообщения самопроверок. Например: если случился выход за пределы массива, самопроверка скажет: обращался к 7-му элементу из 5-и — но не подскажет, кто именно совершил выход. Но если подняться на несколько стековых фреймов выше, часто ошибка становится легко заметной. Новая библиотека, как и многое из Си++, позаимствована из BOOST.
 Впоследствии объекту stacktrace придумали стандартную функцию форматирования.
 Как связывать stacktrace с выпадающими авариями — пока не придумали, ведь то и другое — довольно тяжёлые части языка и библиотеки.
 std::function стал одной из самых «тяжёлых» частей библиотеки STL. Избавившись от нескольких возможностей — нельзя копировать, отсутствуют поля target и target_type — можно получить значительно более лёгкий объект. И, разумеется, этот объект может работать с некопируемыми перехватами.
 В объекте синхронизации «барьер» теперь предполагается, что поток-координатор и ожидающие барьера потоки-клиенты должны вызвать wait, а потоки-работники — arrive при обычном исполнении и arrive_and_drop, если поток выходит из параллельного вычисления. Координационная функция может выполниться в любом потоке либо в последнем arrive, либо в wait.
 Что будет, если никто не вызовет wait (то есть координатора нет),— зависит от реализации. Связано с разнородным аппаратным обеспечением — координатор работает на одной архитектуре, а потоки-работники на другой.
 У обработки ошибок есть четыре важных свойства:
 Главный недостаток исключений — незаметность. У кодов ошибок как минимум грязный код, и они забивают важный канал — возвращаемое значение.
 expected — напомнающий variant тип, который может хранить или значение при нормальном исполнении, или ошибку.
 Чистый код достигается через монадный интерфейс. Общая монада в Си++23 не попала, однако optional и expected обзавелись похожими функциями.
 Монада — стандартная возможность функциональных языков произвести последовательность действий.
 В математике последовательность функций записывается как 
  
    
      
        h
        (
        g
        (
        f
        (
        x
        )
        )
        )
      
    
    {\displaystyle h(g(f(x)))}
  
, что не всегда удобно — в программировании часто лучше x.f().g().h().
 std::optional — довольно простая обёртка, смысл которой — хранить объект или ничего. Проверки на «ничего» занимают немалую часть работы с optional — а что, если в процессе преобразований картинки на ней не окажется кота? А что, если нет места, куда пририсовать бантик?
 Впоследствии то же придумали для expected.
 Существовал strstream — поток данных, работающий на массиве ограниченной длины. Из-за угрозы переполнений запрещён уже в C++98, предложен другой похожий механизм.
 Изначально было: std::cout << std::format("Hello, {}! You have {} mails", username, email_count);
 Это…
 Доступен более лёгкий std::print("Привет, {}! У вас {} писем", username, email_count);.
 Впоследствии уточнили, что print синхронизирован с другими методами вывода в консоль.
 У всех этих типов неявная единица: целая часть мантиссы 1,xxx только подразумевается и не хранится.
 Математические функции должны иметь обёртки для всех поддерживаемых типов — при этом реальный расчёт может вестись в более или менее точном типе.
 Используется для экстремальной оптимизации на стыке строк и низкоуровневых API:
 Возникнет вопрос: а что при этом соптимизировали по сравнению с двумя resize? Дело в том, что стоимость выделения памяти мало зависит от длины буфера, и в большинстве случаев на буфер будет выделено значительно больше памяти, чем реально потребуется на сжатую строку. Новая функция не инициализирует буфер, и ушло зануление очень длинного участка памяти — memset( compressed.data(), '\0', compressed.size()).
 Помимо стандартного форматирования новых объектов (описано в соответствующих разделах), там есть:
 Это указание, что при нормальной работе кода в данную точку попасть нельзя.