C++23

C++23 — ожидаемый стандарт языка программирования C++.

Запрещены и удалены

Удалены

  • Сбор мусора (Си++11) — по факту никто его не поддерживал.
  • Добавлен псевдоконструктор string[_view] (nullptr_t) = delete — как подсказка: строить строку из пустого указателя запрещено.

Запрещены

  • aligned_storage, aligned_union (Си++11) — они ошибкоопасны, их сложно объединять в более крупные конструкции. Замена — alignas[1].
  • Свойства дробного numeric_limits::has_denorm и has_denorm_loss — поскольку поведение денормализованных чисел на аппаратном уровне (и даже при компиляции и исполнении) бывает разное, никто не полагается на это свойство. Что взамен — пока разрабатывается[2]. Ранее аналогичные макросы запретили в Си.
  • Запрещается делать свои allocator_traits. Свойствами аллокатора можно управлять, выставляя его внутренние поля[3].

Снят запрет

  • Операции += и другие с volatile-переменными (запрещены в Си++20). В микроконтроллерах часто используются многобитные порты, спроецированные в память[4], потому разрешили побитовые операции &=, |=, ^= (остальных не нашли в открытом коде). В последний момент разрешили и остальные[5].
  • Присваивание char[] ← UTF-8 const char* s = u8"123"; (см. ниже).

Язык

Мелкие изменения

  • Препроцессорные директивы #elifdef и #elifndef[6].
  • Предварительный оператор if/цикла может быть using-псевдонимом: for (using T = int; T e : v). В Си++20 работало for (typedef int T; T e : v), ведь typedef — синтаксически определение переменной[7].
  • Разрешён код int a[3]; auto (*p)[3] = &a; — auto теперь позволяет указатели и ссылки на массивы[8].
  • Новый литеральный суффикс 123z (знаковый эквивалент size_t), 123uz (size_t).
  • В лямбда-функции без параметров допустимо опускать круглые скобки, если есть ключевое слово mutable и другие подобные: [s2 = std::move(s2)] mutable {}[9].
  • Разрешены повторы атрибутов[10] — оказалось, они часто генерируются макросами.
  • Разрешено неявное преобразование int→bool в static_assert, if constexpr, explicit(bool) и noexcept(bool)[11]. Но: если в explicit/noexcept этот int будет равен чему угодно, кроме 0 и 1,— ошибка. Дробные числа, указатели и прочие объекты запрещены.
  • Хвостовой возвращаемый тип в лямбда-функциях сначала смотрит в перехваты, и только потом — в окружающий текст[12]: auto counter1 = [j=0]() mutable -> decltype(j) { return j++; };
  • Разрешены атрибуты у лямбда-функций[13]: auto lm = [][[nodiscard, vendor::attr]]()->int { return 42; };
  • При наследовании конструктора наследуются и подсказки по автоопределению параметров шаблона (deduction guides)[14].
  • Лямбда-функции и инстанцированные constexpr-шаблоны могут вызывать consteval и тогда сами становятся consteval[15]. Для первой никак нельзя указать, что она consteval. А шаблон теперь может стать условным consteval в зависимости от пути инстанцирования.

if consteval

Более раннее std::is_constant_evaluated(), сделанное встроенной функцией компилятора, оказалось ошибкоопасным[16]. Например:

constexpr size_t strlen(char const* s) {
    //if constexpr (std::is_constant_evaluated()) {  Было, не вызывало ассемблерную версию
    if consteval {    // Стало
        for (const char *p = s; ; ++p) {
            if (*p == '\0') {
                return static_cast<std::size_t>(p - s);
            }
        }    
    } else {
        __asm__("Нечто оптимизированное на SSE");
    }
}

Конечно, компиляторы выдают предупреждение, но неочевидно, что делать — правильно if (std::is_constant_evaluated()), иначе оптимизированная ассемблерная версия вообще не запустится.

Вторая причина — взаимодействие между constexpr и consteval.

consteval int f(int i) { return i; }

constexpr int g(int i) {
    // if (std::is_constant_evaluated()) {   Было, не компилировалось
    if consteval {   // Стало
        return f(i) + 1;
    } else {
        return 42;
    }
}

Этот код вообще не компилировался — consteval-функцию отсюда вызывать нельзя.

Фигурные скобки в then-части обязательны, в else- могут опускаться. Писать вроде if (consteval && n < 0) { невозможно, ради этого старую функцию не запретили.

auto(x) — временная копия объекта

Простой способ получить объект как временный, например[17]:

void pop_front_alike(Container auto& x) {
    std::erase(x.begin(), x.end(), auto(x.front()));
}

x.front() — ошибка: в зависимости от контейнера, эта ссылка будет смотреть или на другой объект, или в пустую память.

Нижеприведённый код корректен, но ревизор может соблазниться ошибочно убрать переменную a.

auto a = x.front();
std::erase(x.begin(), x.end(), a);

В шаблонном программировании этот тип бывает получить непросто:

using T = std::decay_t<decltype(x.front())>;
std::erase(x.begin(), x.end(), T(x.front()));

Название prvalue_cast было отброшено по двум причинам: prvalue — сильно техническое понятие, и не соответствующее названию поведение для массивов (даст указатель).

Также допустимо auto{x}.

Многомерная операция индексирования (квадратные скобки)

Существующие методы[18]:

array(1, 2, 3, 4, 5) = 42;   // выглядит ужасно
array[{1, 2, 3, 4, 5}] = 42; // очень непонятно и неприятно писать
array[1][2][3][4][5] = 42;   // чуть лучше, но под капотом творится просто жуть

Пока только для пользовательских типов[19].

int buffer[2*3*4] = { };
auto s = std::mdspan<int, std::extents<2, 3, 4>> (buffer);
s[1, 1, 1] = 42;

Разные библиотеки реализуют недостающий синтаксис по-разному, но в любом случае это не сочетается с синтаксисом стандартных массивов, и затрудняет автоматический поиск ошибок и инлайнинг (развёртывание функции прямо в вызывающий код).

Предметом дискуссий остаются: нужно ли это для стандартных массивов; нужно ли ослабить требования к operator[] и разрешить его за пределами класса.

This-параметры

Одна из возможностей Си++ — const-корректность — приводит к дублированию кода или написанию способов делегирования. Предлагается решение этого через шаблоны[20]

///// БЫЛО /////
class TextBlock {
public:
  char const& operator[](size_t position) const {
    // ...
    return text[position];
  }

  char& operator[](size_t position) {
    return const_cast<char&>(
      static_cast<TextBlock const&>
        (this)[position]
    );
  }
  // ...
};

///// СТАЛО /////
class TextBlock {
public:
  template <typename Self>
  auto& operator[](this Self&& self, size_t position) {
    // ...
    return self.text[position];
  }
  // ...
};

Методы-расширения пока не предлагаются, но будут возможны в дальнейшем.

Снижение требований к constexpr

Список послаблений большой и связан с двумя вещами:

  • Теперь constexpr означает, что есть хотя бы один путь исполнения, возможный при компиляции.
  • Библиотеки всегда отстают от языка.

Таким образом, теперь возможно написать constexpr-функцию, которая ни при одном наборе аргументов не сможет выполниться при компиляции[21].

Также в constexpr-функциях разрешены goto, переменные нелитеральных типов, статические/внутрипоточные переменные. Если при компиляции будет пройдена любая из этих строк, функция вычисляется при выполнении. __cpp_constexpr поднят до 202103L[22].

В constexpr-функциях теперь могут участвовать указатели и ссылки с неизвестным значением, если этими значениями не пользоваться — оказалось, что std::size не может работать со ссылками[23]:

// Самодельный std::size для массивов, в STL G++ похожий код
template <typename T, size_t N>
constexpr size_t array_size(T (&)[N]) { return N; }

void check(int const (&param)[3]) {
    int local[] = {1, 2, 3};
    constexpr auto s0 = array_size(local); // OK
    constexpr auto s1 = array_size(param); // Теперь OK
}

Разрешены static constexpr-переменные в constexpr-функциях[24]:

constexpr char xdigit(int n) {
  static constexpr char digits[] = "0123456789abcdef";
  return digits[n];
}

Это позволит, например, написать constexpr from_chars[25].

Статические op() и op[]

Убирает одну машинную команду, если класс без данных и инлайнинг не получился[26]. Например, в самобалансирующемся дереве с нестандартным порядком (было в Си++03) и разнородным поиском (Си++14) возможен такой код:

struct CustomCompare {
    using is_transparent = int; // разнородный поиск
    static bool operator() (std::string_view a, std::string_view b) // было const, стало static
        { return someCustomLess(a, b); }
};

std::set<std::string, CustomCompare> things;

Изначальное предложение касалось операции «вызов» operator(). Потом позволили делать статической и операцию «индекс» operator[][27].

Эти операции всё ещё нельзя определять вне класса.

Аннотация [[assume(bool)]]

Разрешено аннотировать только пустой оператор. Код в аннотации никогда не исполняется, даже если имеет побочные эффекты. Служит исключительно для оптимизатора — он может закладываться на данное выражение. Если выражение будет равняться false — неопределённое поведение. Функция __assume/__builtin_assume уже есть в MSVC и Clang, а вот G++ эмулирует её через builtin_unreachable и потому вычисляет выражение внутри.

int divide_by_32(int x) {
    [[assume(x >= 0)]];
    return x/32;  // компилятор может не закладываться на отрицательный x
}

В данном примере, если x неотрицательный, можно делать лёгкую команду shr (беззнаковый сдвиг) или sar (знаковый — такой сдвиг равноценен делению с округлением вниз, в то время как операция / округляет к нулю). Если закладываться на отрицательный — то тяжёлую команду div (деление) или нетривиальные оптимизации.

; G++ x64 trunk (ноябрь 2022) -std=c++2b -O3
; Без assume — знаковое деление на 32 без «тяжёлых» операций: ветвлений и div
test    edi, edi       ; рассчитать флаги для edx≡x
lea     eax, [rdi+31]  ; загрузка x+31; он будет использоваться, если x<0
cmovns  eax, edi       ; загрузка x, если x⩾0
sar     eax, 5         ; битовый сдвиг
ret

; С assume — битовый сдвиг, расходящийся с делением, если x<0
mov     eax, edi       ; требуется по соглашению вызова: параметр в rdx, результат в rax
sar     eax, 5         ; битовый сдвиг
ret

Если при constexpr-счёте окажется, что assume не выполняется — поведение остаётся за компилятором: он может как выдать ошибку, так и ничего не сделать. Это не первая вещь, где поведение в constexpr за компилятором — также за компилятором будет распаковка переменных параметров Си (на манер функции printf) и расчёты с неопределённым поведением[28].

Новые правила синтезированной op!= и перевёрнутых op==/!=

Чтобы писать меньше нетворческого кода, в Си++20 сделали синтез операции «не равняется» из «равняется», и примерку обеих как в обычном виде, так и в перевёрнутом. Это сильно ударило по имевшемуся коду: на G++ работает с предупреждением, например, такой рекурсивный шаблон[29] — выбирает между простой и перевёрнутой операцией ==:

template <typename T>
struct Base {
    bool operator==(const T&) const { return true; }
    bool operator!=(const T&) const { return false; }
};
   
struct Derived : Base<Derived> { };
   
bool b = Derived{} == Derived{};

Теперь синтез операции «не равняется» и переворот происходит, если возвращаемый тип bool и программист не написал операцию != сам. В некоторых случаях компилятор может запутаться и сказать: есть выбор между обычной и перевёрнутой операцией «равняется», и исправление этой ошибки простое — поступить по старинке и написать операцию «не равняется» самостоятельно.

Кодировки символов

Допустимые символы в идентификаторах

В идентификаторах теперь допустимы символы из множеств Юникода XID_Start (начальный) и XID_Continue (остальные).

  • Разрешены буквы и цифры разных алфавитов, включая китайские иероглифы, клинопись и математические буквы латиницы/арабицы, многие из буквоподобных символов.
  • Разрешены символы типа «буква/модифицирующая» — 02C6 ˆ «модификатор-крышка» разрешён, а 02DA ˚ «верхний кружок» имеет тип «символ/модифицирующий» и запрещён.
  • Разрешены комбинирующие метки, включая селекторы начертания.
  • Запрещены эмодзи, неалфавитные символы из техники и математики, форматирующие символы (невидимые символы, отвечающие за обработку текста, в том числе ZWJ и ZWNJ).

Идентификатор должен быть нормализован по алгоритму «каноническая композиция» (NFC, разобрать монолитные символы на компоненты и собрать снова). Если нет — программа некорректна.

Это изменение только делает поддержку Юникода более целостной, но никак не решает вопросов атак через внешне одинаковые строки[30]. Методы передачи таких символов в линкер остаются за реализацией.

Запрещены многосимвольные и некодируемые wchar_t-литералы

Разные компиляторы действовали по-разному на L'\U0001F926' (эмодзи «фейспалм») на двухбайтовом wchar_t (Windows), L'ab'. Теперь оба запрещены[31].

Многосимвольные char-литералы продолжают работать, имеют тип int. Сколько допускается символов и как они будут собраны в одно число — определяется реализацией.

Понятия «кодировка трансляции», «кодировка исполнения»

Узаконено, что одна может отличаться от другой[32], и wchar_t — это единица широкой, зависящей от реализации кодировки исполнения[33].

UTF-8 как кроссплатформенная кодировка трансляции должна поддерживаться безусловно, всеми компиляторами[34]. Метка порядка байтов игнорируется, кроме случаев, когда она противоречит флагам компилятора. Если файл опознан как UTF-8, в нём не должно быть некорректных кодовых комбинаций — однако могут быть корректные комбинации, соответствующие не существующим пока символам.

Числовые значения символьных литералов в препроцессоре совпадают с кодировкой исполнения

Раньше это было за реализацией, но оказалось, что основное назначение этой функции — определение кодировки исполнения[35]. Например, код из SQLite:

/* Проверка, использует ли машина EBCDIC.
   (Да, верите или нет, ещё есть машины, использующие EBCDIC.) */
#if 'A' == '\301'
# define SQLITE_EBCDIC 1
#else
# define SQLITE_ASCII 1
#endif

Все крупные компиляторы фактически работают именно так.

Снова разрешено инициализировать массивы char и unsigned char литералом UTF-8

Все три строчки сломаны в Си++20, снова работают в Си++23[36].

const char* a = u8"a";
const char b[] = u8"b";
const unsigned char c[] = u8"c";

Как оказалось, подобный слом усложнял constexpr-функции, мешал совместимости с Си.

Новые экранировки

"\u{1F926}" для кодовой позиции Юникода, "\o{123}" для восьмеричной системы и "\x{AB}" для шестнадцатеричной[37].

Разрывать такие экранировки ("\x{4" "2}") запрещено.

"\N{LATIN CAPITAL LETTER A WITH MACRON}" позволяет обратиться к символу по юникодному имени[38].

format перекодирует Юникод в однобайтовую кодировку

Исключает крокозябры в таком коде[39]:

std::locale::global(std::locale("Russian.1251"));
auto s = std::format("День недели: {}", std::chrono::Monday);

Набор возможных кодировок определяется реализацией. При неспособности — выбрасывается исключение.

Уточнены понятия «символ» и «ширина»

Для каждого текста реализация сама определяет, сколько позиций терминала он займёт. Описана эталонная юникодная реализация: большинство восточноазиатских символов и эмодзи имеют ширину 2, все умляуты — 0, у остальных 1. Кодировка локаленезависима. Ширина символа-заполнителя всегда принимается за 1[40].

Специальные числа вроде NaN и не заполняются нулями.

// Ширина эмодзи — 2
string sB = format("{:🤡^6}", "x");         // 🤡🤡x🤡🤡🤡
string sC = format("{:*^6}", "🤡🤡🤡");    // 🤡🤡🤡
double inf = numeric_limits<double>::infinity();
string s4 = format("{:06}", inf);           // ␣␣␣inf

Редакционные правки

  • При объединении строк через обратную косую черту теперь допустимы пробелы после этой черты[41]. Так действовали GCC, Clang и ICC — а MSVC оставлял пробел.
  • Компилятор лишён права на перестановку полей одного объекта, если те имеют ненулевую длину и разные права доступа[42]. Так действовали MSVC, GCC, Clang.
  • Запрещена конкатенация строк с противоречивыми префиксами кодировки вроде L"" u"". Из крупных компиляторов такое поддерживает только SDCC — берёт первый из префиксов[43].
  • Узаконена директива #warning, поддерживаемая всеми[44].
  • Упрощены правила неявного перемещения при возврате из функции[45].
  • Переработаны производителезависимые расширенные целые типы[46].
  • Уточнён статус заголовочных файлов Си: изначально они были запрещённые, теперь — для совместимости. Файл, который не должен быть одновременно допустимым файлом Си, не должен их подключать[47]. Это убирает угрозу: в ближайшее время эти заголовки не удалят.
  • span и string_view теперь TriviallyCopyable[48].
  • В модулях запрещено экспортировать бессмысленные вещи вроде static_assert[49]. Однако составные конструкции, состоящие из static_assert, для простоты экспортировать можно: export { static_assert(true); }. (Например, в определённых настройках препроцессора в скобках остался один static_assert.)
  • Стандартные атрибуты можно игнорировать[50]. Единственный из них, который влияет на работу программы,— [[no_unique_address]], и его игнорировать можно было и раньше. Остальные влияют на предупреждения или дают дополнительную информацию компилятору. А то, что влияет на программу, например, alignas,— не атрибут.

Уточнено, какие временные объекты сохраняются до конца цикла

В конструкции «for по объекту» говорится: каждый временный объект (кроме переданного по значению параметра функции) сохраняется до конца цикла[51].

using T = std::list<int>;
const T& f1(const T& t) { return t; }
const T& f2(T t)        { return t; }
T g();
void foo() {
  for (auto e : f1(g())) {}  // Теперь OK, жизнь объекта g() продлевается
  for (auto e : f2(g())) {}  // Всё ещё неопределённое поведение
}

Гармонизация с Си

  • Разрешена метка без оператора: { goto a; ++x; a: }[52].
  • Поддержка <stdatomic.h>. Аналога <cstdatomic> нет[53].
  • Снова разрешено инициализировать массивы char и unsigned char литералом UTF-8 (описано выше).
  • Уточнён статус заголовочных файлов Си, снята угроза их ичезновения (описано выше).
  • Добавлен новый бит метода открытия файла на запись ios::noreplace: файла не должно существовать. Это исключает перезапись нужного файла, исключает гонки за файл между двумя программами. Бит существовал во многих реализациях до Cи++98, и эквивалентен флагу x Си11 (FILE* f = fopen("fname.ext", "wx");). Например, может служить для поиска неиспользуемого временного файла[54].

Библиотека

Мелкие изменения

  • Семейство констант is_scoped_enum — например, для отслеживания миграции библиотеки со старых enum на новые enum class[55].
  • Функция to_underlying для преобразования enum int, более понятная по названию и менее ошибкоопасная[56].
  • Функция byteswap в заголовке <bit> для смены порядка байтов в числах[57].
  • iostream теперь может печатать volatile-указатели — точно так же, как и обычные[58].
  • forward_like — аналог forward для объекта и его поля; понадобился из-за this-параметров[59].
  • Зарезервированы два модуля: import std; (всё пространство имён std::) и import std.compat; (функции совместимости вроде ::fopen из стандартного пространства имён)[60]. На ноябрь 2022 модулей не поддерживает никто, но, по заявлениям «Открытых систем», даже компиляция Hello World серьёзно ускорилась[61].
  • Переписаны концепции equality_comparable_with и другие, чтобы поддерживали некопируемые типы[62].
  • Больше совместимости между кортежами (tuple) и кортежеподобными объектами (парами, статическими массивами)[63].
  • mdspan — нехранящий многомерный массив[64]
  • Усовершенствован visit для работы с типами, унаследованными от variant (обычно какие-то реализации конечных автоматов)[65].
  • Уточнено, что common_reference_t — всегда ссылка, а не reference_wrapper[66].

stacktrace

Одно из важнейших нововведений Си++23, позволяющее видеть ошибку вернее, чем короткие сообщения самопроверок[67]. Например: если случился выход за пределы массива, самопроверка скажет: обращался к 7-му элементу из 5-и — но не подскажет, кто именно совершил выход. Но если подняться на несколько стековых фреймов выше, часто ошибка становится легко заметной. Новая библиотека, как и многое из Си++, позаимствована из BOOST.

#include <algorithm>
#include <iostream>
#include <stacktrace>
 
int main()
{
    auto trace       = std::stacktrace::current();
    auto empty_trace = std::stacktrace{};
 
    // Print stacktrace.
    std::for_each(trace.begin(), trace.end(),
                  [](const auto& f) { std::cout << f << '\n'; });
 
    if (empty_trace.begin() == empty_trace.end())
        std::cout << "stacktrace 'empty_trace' is indeed empty.\n";
}

Впоследствии объекту stacktrace придумали стандартную функцию форматирования[68].

Как связывать stacktrace с выпадающими авариями — пока не придумали, ведь то и другое — довольно тяжёлые части языка и библиотеки.

move_only_function

std::function стал одной из самых «тяжёлых» частей библиотеки STL. Избавившись от нескольких возможностей — нельзя копировать, отсутствуют поля target и target_type — можно получить значительно более лёгкий объект[69]. И, разумеется, этот объект может работать с некопируемыми перехватами.

Аппаратно-разнородные барьеры

В объекте синхронизации «барьер» теперь предполагается, что поток-координатор и ожидающие барьера потоки-клиенты должны вызвать wait, а подчинённые потоки — arrive при обычном исполнении и arrive_and_drop, если поток выходит из параллельного вычисления. Координационная функция может выполниться в любом потоке либо в последнем arrive, либо в wait[70].

Что будет, если никто не вызовет wait (то есть координатора нет),— зависит от реализации. Связано с разнородным аппаратным обеспечением — координатор работает на одной архитектуре, а потоки-работники на другой.

expected

У обработки ошибок есть четыре важных свойства:

  • Заметность: ревизору должны быть видны нарушения методики использования функции.
  • Информация об ошибках: ошибка должна нести достаточно информации о том, откуда она взялась и как её решить.
  • Чистый код: код обработки ошибок должен быть минимален.
  • Невмешательство: ошибки не должны забивать какой-нибудь канал, предназначенный для нормального хода исполнения.

Главный недостаток исключений — незаметность. У кодов ошибок как минимум грязный код, и они забивают важный канал — возвращаемое значение[71].

expected — напомнающий variant тип, который может хранить или значение при нормальном исполнении, или ошибку.

expected<int, errc> getIntOrZero(istream_range& is) {
    auto r = getInt(is);   // возвращает такой же expected
    if (!r && r.error() == errc::empty_stream) {
        return 0;
    }
    return r;
}

Чистый код достигается через монадный интерфейс. Общая монада в Си++23 не попала, однако optional и expected обзавелись похожими функциями.

Монадные операции над optional/expected

Монада — стандартная возможность функциональных языков произвести последовательность действий.

В математике последовательность функций записывается как , что не всегда удобно — в программировании часто лучше x.f().g().h().

std::optional — довольно простая обёртка, смысл которой — хранить объект или ничего. Проверки на «ничего» занимают немалую часть работы с optional — а что, если в процессе преобразований картинки на ней не окажется кота? А что, если нет места, куда пририсовать бантик?[72]

std::optional<image> get_cute_cat (const image& img) {
    return crop_to_cat(img)               // image → optional; [nullopt] на картинке нет кота
           .and_then(add_bow_tie)         // image → optional; [nullopt] некуда добавить бантик
           .and_then(make_eyes_sparkle)   // image → optional; [nullopt] не видно глаз
           .transform(make_smaller)       // image → image
           .transform(add_rainbow);       // image → image
}

Впоследствии то же придумали для expected[73].

spanstream — замена запрещённому в Си++98 strstream

Существовал strstream — поток данных, работающий на массиве ограниченной длины. Из-за угрозы переполнений запрещён уже в Си++98, предложен другой похожий механизм.

char output[30]{};
ospanstream os{span<char>{output}};
os << 10 << 20 << 30;
auto const sp = os.span();
ASSERT_EQUAL(6,sp.size());
ASSERT_EQUAL("102030",std::string(sp.data(),sp.size()));
ASSERT_EQUAL(static_cast<void*>(output),sp.data()); // никакого копирования данных
ASSERT_EQUAL("102030",output); // гарантируется нуль-терминирование

print

Изначально было: std::cout << std::format("Hello, {}! You have {} mails", username, email_count);

Это…

  • Удлиняет двоичный код — потоки изначально тяжелы.
  • Нет поддержки Юникода.
  • Выглядит некрасиво.

Доступен более лёгкий std::print("Привет, {}! У вас {} писем", username, email_count);[74].

Впоследствии уточнили, что print синхронизирован с другими методами вывода в консоль[75].

Необязательные дробные типы

Название Битов мантиссы Битов порядка Примечание
float16_t 10 + неявная 1 5 Соответствует IEEE binary16
bfloat16_t 7 + неявная 1 8 Верхние два байта IEEE binary32 (≈float), используется в ИИ-библиотеках, отсюда имя — brain float
float32_t 23 + неявная 1 8 Соответствует IEEE binary32, большинству реализаций float
float64_t 52 + неявная 1 11 Соответствует IEEE binary64, большинству реализаций double
float128_t 112 + неявная 1 15 Соответствует IEEE binary128

Математические функции должны иметь обёртки для всех поддерживаемых типов — при этом реальный расчёт может вестись в более или менее точном типе[76].

Новая функциональность диапазонов (ranges) и представлений (views)

  • Из концепции view удалена инициализация без параметров[77].
  • Уточнены требования к дипазонам[78], представлениям[79], адаптерам диапазонов[80].
  • Механизмы для написания собственных адаптеров диапазонов[81]
  • Семейство адаптеров zip для параллельного прохождения разных диапазонов[82].
  • ranges::to — преобразование из диапазона в контейнер[83].
  • Функции iota, shift_left, shift_right[84].
  • Функции chunk и slide[85].
  • Функция chunk_by[86].
  • Функция join_with[87].
    • Улучшена работа join[_with] с итераторами, которые возвращают ссылку на что-то внутри самого итератора[88].
  • Функции starts_with, ends_with[89].
  • split_view переименован в lazy_split_view, добавлен новый split_view[90].
  • Ослаблены ограничения на join_view[91].
  • string_view можно строить из непрерывного диапазона[92]. Впоследствии уточнили: конструктор явный (explicit)[93].
  • Механизмы вывода диапазонов функцией format[94].
  • Добавлены механизмы указания, как печатать нестандартный диапазон функцией format — вывод запрещён, как отображение (map), как множество (set), как кортеж (sequence), как строку (string), как отладочное сообщение (debug_string).[95].
  • generator — синхронный генератор диапазонов на сопрограммах[96]. Налажено форматирование подобных объектов[97].
  • single_view и другие адаптеры-представления теперь могут работать с перемещаемыми, но не копируемыми типами[98].
  • Новая функция-адаптер views::repeat, повторяющая один объект N раз или до бесконечности[99].
  • Переписаны требования к алгоритмам, чтобы они могли пользоваться итераторами диапазонов[100].
  • Функция cartesian_product — декартово произведение диапазонов[101][102].
  • Функция ranges::fold, представляющая собой f(f(…f(f(init, x1), x2), …), xn)[103].
  • Функции ranges::contains, ranges::contains_subrange[104].
  • Объект stride_view, функция views::stride — представление с шагом N[105]
  • Функции ranges::find_last(), find_last_if(), find_last_if_not[106].
  • Функция views::enumerate[107] — часто функцией Си++ «проход по контейнеру» не пользовались просто потому, что вдобавок требовался номер в последовательности.

Новые подсказки по выведению параметров шаблона (deduction guides)

  • function — теперь будет компилироваться std::function f = less<int>{};, если less — шаблон с новой статической операцией «вызов»[26].
  • function и packaged_task — теперь будет компилироваться std::function g = F{};, если F — объект, чья операция «вызов» содержит новый this-параметр[108]

Новая функциональность строк владеющих (string) и невладеющих (string_view)

  • string[_view].contains — часто надо проверить на наличие подстроки, не выясняя, где совпадение[109].
  • string.substr() && (с временным this) — для оптимизации auto a = someTemporaryString().substr(0, 2);[110].
  • string_view можно строить из непрерывного диапазона (см. выше).
  • Добавлен псевдоконструктор string[_view](nullptr_t) = delete — как подсказка: строить строку из пустого указателя запрещено.

string::resize_and_overwrite

Используется для экстремальной оптимизации на стыке строк и низкоуровневых API:

int compress(void* out, size_t* out_size, const void* in, size_t in_size);

std::string CompressWrapper(std::string_view input) {
    std::string compressed;

    compressed.resize_and_overwrite(input.size(), [input](char* buf, std::size_t n) noexcept {
        std::size_t compressed_size = n;
        auto is_ok = compress(buf, &compressed_size, input.data(), input.size());
        assert(is_ok);
        return compressed_size;
    });

    return compressed;
}

Возникнет вопрос: а что при этом соптимизировали по сравнению с двумя resize?[18] Дело в том, что стоимость выделения памяти мало зависит от длины буфера, и в большинстве случаев на буфер будет выделено значительно больше памяти, чем реально потребуется на сжатую строку. Новая функция не инициализирует буфер, и ушло зануление очень длинного участка памяти — memset( compressed.data(), '\0', compressed.size()).

Новая функциональность итераторов

  • Адаптер move_iterator — теперь итератор того же вида (односторонний/двусторонний/произвольного доступа), что и исходный итератор (раньше только односторонний)[111].
  • Переписаны требования к алгоритмам, чтобы они могли пользоваться итераторами диапазонов (см. выше).
  • cbegin всегда должен возвращать константный итератор[112].
  • Исправлена iterator_category в counted_iterator и некоторых других, чтобы из них можно было собирать сложные диапазоны[113].
  • Придумана концепция позаимствованных диапазонов — их итераторами можно продолжать пользоваться, когда объект-диапазон исчезает[114].

Новая функциональность контейнеров

  • Разнородный extract и erase в ассоциативных контейнерах[115]. Например, ключ хранения string, а ключ доступа — string_view.
  • Новая функция Allocator.allocate_at_least, более полно использующая особенности механизма выделения памяти[116]. Контейнеры переменного размера понемногу будут переходить на неё.
  • Новые контейнеры flat_[multi]set и flat_[multi]map, работающие как минимум на шаблонах vector, deque, list. Представляют собой простые сортированные массивы.

Новые constexpr

  • Больше constexpr в bitset[117].
  • Целочисленные from_chars, to_chars[118].
  • Почти все функции unique_ptr[119].
  • Математика из cmath, cstdlib[120].
  • Большинство функций optional, variant[121].

Новая функциональность format

Помимо стандартного форматирования новых объектов (описано в соответствующих разделах), там есть:

  • Форматирование thread::id[68].
  • Проверка корректности форматирования при компиляции, если такое возможно; облегчение format_to, если обработка при компиляции удастся[122].
  • Открыт format_string.get() — например, для сложного логирования, когда сообщение генерирует одна система, а переводит в строку — другая (в сетевом или локализованном коде). На запуске будет вызван обычный «тяжёлый» format (неизвестно, что придёт из перевода или по сети), зато уже при компиляции будет проверена корректность строки-прототипа (для локализации) или посланного в сеть[123].

Оптимизации и предупреждения

  • exchange получил условный noexcept — если объект создаётся с перемещением и присваивается (с перемещением или по копии, в зависимости от правого параметра), не вызывая исключений[124].
  • apply получил такой же условный noexcept[125].
  • Некоторые библиотечные функции наподобие malloc и bit_cast ещё с Си++20 получили прозвище «благословенные» — они могут неявно создавать объекты. То есть: не оперируя типом X, тем не менее, подразумевают, что в памяти может появиться объект типа X. Теперь можно «благословить» любую функцию кодом X* p = std::start_lifetime_as<X>(myMalloc(sizeof(struct X)); — и подобные вызовы не будут неопределённым поведением[126].
    • Типы, с которыми такие «благословенные» функции могут работать, названы «типы с неявным временем жизни» — для них придумана функция std::is_implicit_lifetime[127].
  • Новые свойства типов reference_constructs_from_temporary, reference_converts_from_temporary. Некоторые объекты (std::tuple<const std::string&>) теперь не могут конструироваться из подобных объектов[128].
  • Добавлен псевдоконструктор string[_view](nullptr_t) = delete — как подсказка: строить строку из пустого указателя запрещено.

Функция unreachable

Это указание, что при нормальной работе кода в данную точку попасть нельзя[129].

enum class MyBool { NO, YES };
int toInt(MyBool x) {
  switch (x) {
  case MyBool::NO:  return 0;
  case MyBool::YES: return 1;
  }
  // Прикрываем знаменитое предупреждение G++
  std::unreachable();
}

Оставлены на будущее

  • static_assert(false) в шаблонах.
  • #embed — загрузка массива из двоичного файла.
  • Ситуация с Юникодом в диагностических строках (static_assert и других).
  • Атрибуты для структурного связывания auto [a, b [[vendor::attribute]], c] = f();
  • inspect — более мощная версия switch.
  • Стековые сопрограммы (в Си++20 только с бесстековые).

Примечания

  1. Источник. Дата обращения: 8 августа 2022. Архивировано 18 июля 2022 года.
  2. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2614r2.pdf
  3. P2652R2: Disallow User Specialization of allocator_traits
  4. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2327r1.pdf
  5. Core "NB" Issues
  6. Источник. Дата обращения: 20 июля 2022. Архивировано 10 июня 2022 года.
  7. P2360R0: Extend init-statement to allow alias-declaration
  8. CWG Issue 2397
  9. P1102R2: Down with ()! Дата обращения: 9 августа 2022. Архивировано 9 августа 2022 года.
  10. Источник. Дата обращения: 27 июля 2022. Архивировано 24 мая 2022 года.
  11. Narrowing contextual conversions to bool. Дата обращения: 27 июля 2022. Архивировано 27 июля 2022 года.
  12. Change scope of lambda trailing-return-type. Дата обращения: 27 июля 2022. Архивировано 27 июля 2022 года.
  13. Источник. Дата обращения: 27 июля 2022. Архивировано 22 августа 2022 года.
  14. Источник. Дата обращения: 1 августа 2022. Архивировано 30 июля 2022 года.
  15. consteval needs to propagate up
  16. `if consteval`. Дата обращения: 20 июля 2022. Архивировано 20 июля 2022 года.
  17. auto(x): decay-copy in the language - HackMD. www.open-std.org. Дата обращения: 7 апреля 2023.
  18. 1 2 C++23 — feature freeze близко / Хабр. Дата обращения: 28 июля 2022. Архивировано 14 мая 2022 года.
  19. Источник. Дата обращения: 20 июля 2022. Архивировано 24 мая 2022 года.
  20. Deducing this. Дата обращения: 27 июля 2022. Архивировано 12 июля 2022 года.
  21. Relaxing some constexpr restrictions. Дата обращения: 29 июля 2022. Архивировано 25 июля 2022 года.
  22. Non-literal variables (and labels and gotos) in constexpr functions. Дата обращения: 20 июля 2022. Архивировано 24 мая 2022 года.
  23. Using unknown pointers and references in constant expressions
  24. Permitting static constexpr variables in constexpr functions
  25. C++23 — финал, C++26 — начало / Хабр
  26. 1 2 static operator(). Дата обращения: 29 июля 2022. Архивировано 29 июля 2022 года.
  27. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2589r0.pdf
  28. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1774r8.pdf
  29. P2468R2: The Equality Operator You Are Looking For
  30. C++ Identifier Syntax using Unicode Standard Annex 31. Дата обращения: 27 июля 2022. Архивировано 12 июля 2022 года.
  31. Источник. Дата обращения: 27 июля 2022. Архивировано 27 июля 2022 года.
  32. P2314R3: Character sets and encodings. Дата обращения: 27 июля 2022. Архивировано 24 мая 2022 года.
  33. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2460r2.pdf
  34. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2295r6.pdf
  35. Источник. Дата обращения: 27 июля 2022. Архивировано 24 мая 2022 года.
  36. D2513R4: char8_t Compatibility and Portability Fix
  37. Источник. Дата обращения: 29 июля 2022. Архивировано 24 мая 2022 года.
  38. Named universal character escapes. Дата обращения: 29 июля 2022. Архивировано 29 июля 2022 года.
  39. P2419R2: Clarify handling of encodings in localized formatting of chrono types
  40. std::format() fill character allowances; proposed resolution for LWG issues 3576 and 3639
  41. Источник. Дата обращения: 27 июля 2022. Архивировано 24 мая 2022 года.
  42. Источник. Дата обращения: 27 июля 2022. Архивировано 24 мая 2022 года.
  43. P2201R1: Mixed string literal concatenation. Дата обращения: 27 июля 2022. Архивировано 27 июля 2022 года.
  44. Источник. Дата обращения: 27 июля 2022. Архивировано 30 июля 2022 года.
  45. P2266R3: Simpler implicit move. Дата обращения: 1 августа 2022. Архивировано 24 мая 2022 года.
  46. Cleaning up integer-class types
  47. Clarifying the status of the «C headers»
  48. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2251r1.pdf
  49. P2615R0: Meaningful exports
  50. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2552r0.pdf
  51. D2718R0: Wording for P2644R1 Fix for Range-based for Loop
  52. Источник. Дата обращения: 29 июля 2022. Архивировано 17 июня 2022 года.
  53. P0943R6: Support C atomics in C. Дата обращения: 8 августа 2022. Архивировано 8 августа 2022 года.
  54. Support exclusive mode for fstreams
  55. Источник. Дата обращения: 8 августа 2022. Архивировано 24 мая 2022 года.
  56. P1682R3: std::to_underlying for enumerations. Дата обращения: 8 августа 2022. Архивировано 8 августа 2022 года.
  57. P1272R4: Byteswapping for fun&&nuf. Дата обращения: 8 августа 2022. Архивировано 8 августа 2022 года.
  58. P1147R1: Printing `volatile` Pointers
  59. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2445r1.pdf
  60. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2465r3.pdf
  61. Стандарт C++ 23 функционально завершен | Открытые системы. СУБД | Издательство «Открытые системы»
  62. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2404r3.pdf
  63. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2165r4.pdf
  64. MDSPAN
  65. Inheriting from `std::variant`
  66. `common_reference_t` of `reference_wrapper` Should Be a Reference Type
  67. A Proposal to add stacktrace library
  68. 1 2 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2693r1.pdf
  69. P0288R9: move_only_function. Дата обращения: 20 июля 2022. Архивировано 20 июля 2022 года.
  70. barrier’s phase completion guarantees - HackMD
  71. P0323R12: std::expected
  72. p0798R6: Monadic operations for std::optional. Дата обращения: 20 июля 2022. Архивировано 20 июля 2022 года.
  73. P2505R5 Monadic Functions for std::expected
  74. P2093R14: Formatted output. Дата обращения: 29 июля 2022. Архивировано 24 июля 2022 года.
  75. P2539R3: Should the output of std::print to a terminal be synchronized with the underlying stream?
  76. P1467R9: Extended floating-point types and standard names. Дата обращения: 29 июля 2022. Архивировано 29 июля 2022 года.
  77. Views should not be required to be default constructible
  78. Poison Pills are Too Toxic
  79. What is a `view`?
  80. Clarifying range adaptor objects
  81. Pipe support for user-defined range adaptors
  82. zip
  83. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1206r7.pdf
  84. ranges::iota, ranges::shift_left, and ranges::shift_right
  85. Windowing range adaptors: views::chunk and views::slide
  86. views::chunk_by
  87. views::join_with
  88. Stashing stashing iterators for proper flattening
  89. starts\_with and ends\_with
  90. Superior String Splitting
  91. join_view should join all views of ranges
  92. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1989r2.pdf
  93. `string_view` range constructor should be `explicit&grave
  94. Formatting Ranges
  95. Improve default container formatting
  96. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2502r2.pdf
  97. P2418R2: Add support for std::generator-like types to std::format
  98. P2494R2: Relaxing range adaptors to allow for move only types
  99. P2474R2: `views::repeat`
  100. P2408R5: Ranges iterators as inputs to non-Ranges algorithms
  101. P2374R4: `views::cartesian_product`
  102. Empty Product for certain Views
  103. ranges::fold
  104. `std::ranges::contains`
  105. stride_view
  106. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1223r5.pdf
  107. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2164r9.pdf
  108. Issue 3617: function/packaged_task deduction guides and deducing this
  109. string contains function. Дата обращения: 8 августа 2022. Архивировано 8 августа 2022 года.
  110. std::string::substr() &&
  111. move_iterator<T*> should be a random access iterator
  112. cbegin should always return a constant iterator
  113. Repairing input range adaptors and counted_iterator
  114. Conditionally borrowed ranges
  115. P2077R3: Heterogeneous erasure overloads for associative containers. Дата обращения: 29 июля 2022. Архивировано 24 мая 2022 года.
  116. P0401R6: Providing size feedback in the Allocator interface. Дата обращения: 8 августа 2022. Архивировано 20 июля 2022 года.
  117. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2417r2.pdf
  118. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2291r3.pdf
  119. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2273r3.pdf
  120. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0533r9.pdf
  121. Missing `constexpr` in `std::optional` and `std::variant`
  122. P2216R3: std::format improvements
  123. Expose std::$basic-format-string$<charT, Args...>
  124. P2401R0: Add a conditional noexcept specification to std::exchange. Дата обращения: 28 июля 2022. Архивировано 28 июля 2022 года.
  125. Add a conditional noexcept specification to std::apply
  126. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2590r2.pdf
  127. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2674r1.pdf
  128. A type trait to detect reference binding to temporary
  129. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0627r6.pdf

Категории