Русская Википедия:C++23

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

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

На июль 2023 закончен, но окончательная версия не опубликована.

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

Удалены

Запрещены

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

Снят запрет

  • Операции Шаблон:Cpp и другие с Шаблон:Cpp-переменными (запрещены в Си++20). В микроконтроллерах часто используются многобитные порты, спроецированные в память[4], потому разрешили побитовые операции Шаблон:Cpp, Шаблон:Cpp, Шаблон:Cpp (остальных не нашли в открытом коде). В последний момент разрешили и остальные[5].
  • Присваивание char[] ← UTF-8 Шаблон:Cpp (см. ниже).

Язык

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

  • Предварительный оператор if/цикла может быть using-псевдонимом: Шаблон:Cpp. В Си++20 работало Шаблон:Cpp, ведь синтаксически typedef — это определение переменной[6].
  • Разрешён код Шаблон:Cpp — auto теперь позволяет указатели и ссылки на массивы[7].
  • Новый литеральный суффикс Шаблон:Cpp (знаковый эквивалент Шаблон:Cpp), Шаблон:Cpp (Шаблон:Cpp).
  • В лямбда-функции без параметров допустимо опускать круглые скобки, если есть ключевое слово Шаблон:Cpp и другие подобные: Шаблон:Cpp[8].
  • Разрешены повторы атрибутов[9] — оказалось, они часто генерируются макросами.
  • Расширено неявное преобразование int→bool в Шаблон:Cpp и Шаблон:Cpp[10]: всё, кроме нуля, эквивалентно Шаблон:Cpp. В Шаблон:Cpp, как и раньше, годятся только 0 и 1. Дробные числа, указатели и объекты без Шаблон:Cpp, как и раньше, запрещены.
  • Хвостовой возвращаемый тип в лямбда-функциях сначала смотрит в перехваты, и только потом — в окружающий текст[11]: Шаблон:Cpp
  • Разрешены атрибуты у лямбда-функций[12]: Шаблон:Cpp
  • При наследовании конструктора наследуются и подсказки по автоопределению параметров шаблона (deduction guides)[13].
  • Лямбда-функции и Шаблон:Cpp-шаблоны могут вызывать Шаблон:Cpp и тогда сами становятся Шаблон:Cpp[14]. Для первой никак нельзя указать, что она Шаблон:Cpp. А шаблон теперь может стать условным Шаблон:Cpp в зависимости от пути инстанцирования.

if consteval

Более раннее Шаблон:Cpp, сделанное встроенной функцией компилятора, оказалось ошибкоопасным[15]. Например:

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");
    }
}

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

Вторая причина — взаимодействие между Шаблон:Cpp и Шаблон:Cpp.

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- могут опускаться. Писать вроде Шаблон:Cpp невозможно, ради этого старую функцию не запретили.

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

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

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

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

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

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()));

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

Также допустимо Шаблон:Cpp.

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

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

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

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

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

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

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

This-параметры

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

///// БЫЛО /////
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-функцию, которая ни при одном наборе аргументов не сможет выполниться при компиляции[20].

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

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

// Самодельный 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
}

Разрешены Шаблон:Cpp-переменные в Шаблон:Cpp-функциях[23]:

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

Это позволит, например, написать Шаблон:Cpp[24].

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

Убирает одну машинную команду, если класс без данных и инлайнинг не получился[25]. Например, в самобалансирующемся дереве с нестандартным порядком (было в Си++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;

Изначальное предложение касалось операции «вызов» Шаблон:Cpp. Потом позволили делать статической и операцию «индекс» Шаблон:Cpp[26].

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

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

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

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

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

; G++ x64 trunk (ноябрь 2022) -std=c++2b -O3
; Без assume — знаковое деление на 32 без «тяжёлых» операций: ветвлений и div
test    edi, edi       ; рассчитать процессорные флаги (нам важен sf) для 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) и расчёты с неопределённым поведением[27].

Новые правила синтезированной Шаблон:Cpp и перевёрнутых Шаблон:Cpp/Шаблон:Cpp

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

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 и программист не написал операцию != сам. В некоторых случаях компилятор может запутаться и сказать: есть выбор между обычной и перевёрнутой операцией «равняется», и исправление этой ошибки простое — поступить по старинке и написать операцию «не равняется» самостоятельно.

static_assert(false)

В шаблонах (в частности Шаблон:Cpp), бывает нужно сказать: если ни один из вариантов Шаблон:Cpp не выполняется — программа не должна компилироваться. Для этого пытаются запутать компилятор так, чтобы он не увидел вечный Шаблон:Cpp в неинстанцированном шаблоне. Общепринятое решение — шаблон Шаблон:Cpp, который пишут прямо на месте[29].

В новой версии Си++ Шаблон:Cpp в шаблонах рассчитывается при инстанцировании. Если он должен рассчитываться всегда — вытащите его за пределы шаблона.

// Больше не нужно
template <class> inline constexpr bool always_false = false;

template <class T>
void f(T t) {
  if constexpr (sizeof(T) == sizeof(int)) {
    use(t);
  } else {
    static_assert(always_false<T>, "must be int-sized");  // Было
    static_assert(false, "must be int-sized");            // Стало
  }
}

void g(char c) {
  f(0); // OK
  f(c); // error: must be int-sized
}

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

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

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

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

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

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

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

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

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

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

Узаконено, что одна может отличаться от другой[32], и Шаблон:Cpp — это единица широкой, зависящей от реализации кодировки исполнения[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-функции, мешал совместимости с Си.

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

Шаблон:Cpp для кодовой позиции Юникода, Шаблон:Cpp для восьмеричной системы и Шаблон:Cpp для шестнадцатеричной[37].

Разрывать такие экранировки (Шаблон:Cpp) запрещено.

Шаблон:Cpp позволяет обратиться к символу по юникодному имени[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.
  • Запрещена конкатенация строк с противоречивыми префиксами кодировки вроде Шаблон:Cpp. Из крупных компиляторов такое поддерживает только SDCC — берёт первый из префиксов[43].
  • Узаконена директива Шаблон:Cpp, поддерживаемая всеми[44].
  • Упрощены правила неявного перемещения при возврате из функции[45].
  • Переработаны производителезависимые расширенные целые типы[46].
  • Шаблон:Cpp и Шаблон:Cpp теперь TriviallyCopyable[47].
  • В модулях запрещено экспортировать бессмысленные вещи вроде Шаблон:Cpp[48]. Однако составные конструкции, состоящие из Шаблон:Cpp, для простоты экспортировать можно: Шаблон:Cpp. (Например, в определённых настройках препроцессора в скобках остался один Шаблон:Cpp.)
  • Стандартные атрибуты можно игнорировать[49]. Единственный из них, который влияет на работу программы,— Шаблон:Cpp, и его игнорировать можно было и раньше. Остальные влияют на предупреждения или дают дополнительную информацию компилятору. А то, что влияет на программу, например, Шаблон:Cpp,— не атрибут.

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

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

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())) {}  // Всё ещё неопределённое поведение
}

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

  • Препроцессорные директивы Шаблон:Cpp и Шаблон:Cpp, которые будут в Си23[51].
  • Разрешена метка без оператора: Шаблон:Cpp[52].
  • Поддержка Шаблон:Cpp. Аналога Шаблон:Cpp нет[53].
  • Снова разрешено инициализировать массивы char и unsigned char литералом UTF-8 (описано выше).
  • Уточнён статус заголовочных файлов Си: изначально они были запрещённые, теперь — для совместимости. Файл, который не должен быть одновременно допустимым файлом Си, не должен их подключать[54]. Это убирает угрозу: в ближайшее время эти заголовки не удалят.
  • Добавлен новый бит метода открытия файла на запись Шаблон:Cpp: файла не должно существовать. Это исключает перезапись нужного файла, исключает гонки за файл между двумя программами. Бит существовал во многих реализациях до Cи++98, и эквивалентен флагу Шаблон:Cpp Си11 (Шаблон:Cpp). Например, может служить для поиска неиспользуемого временного файла[55].

Обёртки Шаблон:Cpp, Шаблон:Cpp

Эти обёртки призваны «подружить» «сырые» Си-API и умные указатели[56][57]. Исчезая, обёртка перезаписывает указатель. В любом случае в типе умного указателя программист должен верно указать, какой функцией объект уничтожать.

// Межъязыковой API
error_num c_api_create_handle(int seed_value, int** p_handle);
void c_api_delete_handle(int* handle);

// Умная обёртка на Си++
struct resource_deleter {
	void operator()( int* handle ) { c_api_delete_handle(handle); }
};
std::unique_ptr<int, resource_deleter> resource(nullptr);

// Создание объекта через out_ptr
error_num err = c_api_create_handle(24, std::out_ptr(resource));
if (err == C_API_ERROR_CONDITION) {
	// обработка ошибок
}

Библиотека

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

  • Семейство констант Шаблон:Cpp — например, для отслеживания миграции библиотеки со старых Шаблон:Cpp на новые Шаблон:Cpp[58].
  • Функция Шаблон:Cpp для преобразования Шаблон:Cpp, более понятная по названию и менее ошибкоопасная[59].
  • Функция Шаблон:Cpp в заголовке Шаблон:Cpp для смены порядка байтов в числах[60].
  • iostream теперь может печатать volatile-указатели — точно так же, как и обычные[61].
  • Шаблон:Cpp — аналог Шаблон:Cpp для объекта и его поля; понадобился из-за this-параметров[62].
  • Зарезервированы два модуля: Шаблон:Cpp (всё пространство имён Шаблон:Cpp) и Шаблон:Cpp (функции совместимости вроде Шаблон:Cpp из стандартного пространства имён)[63]. На ноябрь 2022 модулей не поддерживает никто, но, по заявлениям «Открытых систем», даже компиляция Hello World серьёзно ускорилась[64].
  • Переписаны концепции Шаблон:Cpp и другие, чтобы поддерживали некопируемые типы[65].
  • Больше совместимости между кортежами (tuple) и кортежеподобными объектами (парами, статическими массивами)[66].
  • Шаблон:Cpp — нехранящий многомерный массив[67]
  • Усовершенствован Шаблон:Cpp для работы с типами, унаследованными от Шаблон:Cpp (обычно какие-то реализации конечных автоматов)[68].
  • Уточнено, что Шаблон:Cpp — всегда ссылка, а не Шаблон:Cpp[69].
  • У библиотеки изначально были два варианта: автономная (freestanding) и платформенная (hosted); автономная не содержит системных вызовов и может писаться даже на чистом Си++. В автономную версию Шаблон:Cpp внесён полностью, а Шаблон:Cpp и Шаблон:Cpp — частично[70].

stacktrace

Одно из важнейших нововведений Си++23, позволяющее видеть ошибку вернее, чем короткие сообщения самопроверок[71]. Например: если случился выход за пределы массива, самопроверка скажет: обращался к 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";
}

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

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

move_only_function

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

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

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

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

expected

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

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

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

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

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

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

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

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

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

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

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
}

Впоследствии то же придумали для Шаблон:Cpp[78].

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

Изначально было: Шаблон:Cpp

Это…

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

Доступен более лёгкий Шаблон:Cpp[79].

Впоследствии уточнили, что Шаблон:Cpp синхронизирован с другими методами вывода в консоль[80].

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

Название Битов мантиссы Битов порядка Примечание
float16_t 10 + Шаблон:Comment 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

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

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

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

  • Шаблон:Cpp — теперь будет компилироваться Шаблон:Cpp, если less — шаблон с новой статической операцией «вызов»[25].
  • Шаблон:Cpp и Шаблон:Cpp — теперь будет компилироваться Шаблон:Cpp, если F — объект, чья операция «вызов» содержит новый (также ожидаемый в Си++23) this-параметр[113]

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

  • Шаблон:Cpp — часто надо проверить на наличие подстроки, не выясняя, где совпадение[114].
  • Шаблон:Cpp (с временным Шаблон:Cpp) — для оптимизации Шаблон:Cpp[115].
  • string_view можно строить из непрерывного диапазона (см. выше).
  • Добавлен псевдоконструктор Шаблон:Cpp — как подсказка: строить строку из пустого указателя запрещено.

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;
}

Возникнет вопрос: а что при этом соптимизировали по сравнению с двумя Шаблон:Cpp?[17] Дело в том, что стоимость выделения памяти мало зависит от длины буфера, и в большинстве случаев на буфер будет выделено значительно больше памяти, чем реально потребуется на сжатую строку. Новая функция не инициализирует буфер, и ушло зануление очень длинного участка памяти — Шаблон:Cpp.

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

  • Адаптер Шаблон:Cpp — теперь итератор того же вида (односторонний/двусторонний/произвольного доступа), что и исходный итератор (раньше только односторонний)[116].
  • Переписаны требования к алгоритмам, чтобы они могли пользоваться итераторами диапазонов (см. выше).
  • Шаблон:Cpp всегда должен возвращать константный итератор[117].
  • Исправлена Шаблон:Cpp в Шаблон:Cpp и некоторых других, чтобы из них можно было собирать сложные диапазоны[118].
  • Придумана концепция позаимствованных диапазонов — их итераторами можно продолжать пользоваться, когда объект-диапазон исчезает[119].

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

Новые constexpr

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

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

  • Форматирование Шаблон:Cpp[72].
  • Проверка корректности форматирования при компиляции, если такое возможно; облегчение Шаблон:Cpp, если обработка при компиляции удастся[127].
  • Открыт Шаблон:Cpp — например, для сложного логирования, когда сообщение генерирует одна система, а переводит в строку — другая (в сетевом или локализованном коде). На запуске будет вызван обычный «тяжёлый» Шаблон:Cpp (неизвестно, что придёт из перевода или по сети), зато уже при компиляции будет проверена корректность строки-прототипа (для локализации) или посланного в сеть[128].

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

  • Шаблон:Cpp получил условный noexcept — если объект создаётся с перемещением и присваивается (с перемещением или по копии, в зависимости от правого параметра), не вызывая исключений[129].
  • Шаблон:Cpp получил такой же условный noexcept[130].
  • Некоторые библиотечные функции наподобие Шаблон:Cpp и Шаблон:Cpp ещё с Си++20 получили прозвище «благословенные» — они могут неявно создавать объекты. То есть: не оперируя типом X, тем не менее, подразумевают, что в памяти может появиться объект типа X. Теперь можно «благословить» любую функцию кодом Шаблон:Cpp — и подобные вызовы не будут неопределённым поведением[131].
    • Типы, с которыми такие «благословенные» функции могут работать, названы «типы с неявным временем жизни» — для них придумана функция Шаблон:Cpp[132].
  • Новые свойства типов Шаблон:Cpp, Шаблон:Cpp. Некоторые объекты (Шаблон:Cpp) теперь не могут конструироваться из подобных объектов[133].
  • Добавлен псевдоконструктор Шаблон:Cpp — как подсказка: строить строку из пустого указателя запрещено.

Функция unreachable

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

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

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

  • Шаблон:Cpp — загрузка массива из двоичного файла.
  • Ситуация с Юникодом в диагностических строках (Шаблон:Cpp и других).
  • Атрибуты для структурного связывания Шаблон:Cpp
  • Шаблон:Cpp — более мощная версия Шаблон:Cpp.
  • Стековые сопрограммы (в Си++20 только с бесстековые).

Примечания

Шаблон:Примечания

Шаблон:C++

  1. Шаблон:Cite web
  2. Шаблон:Cite web
  3. Шаблон:Cite web
  4. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2327r1.pdf
  5. Шаблон:Cite web
  6. P2360R0: Extend init-statement to allow alias-declaration
  7. CWG Issue 2397
  8. Шаблон:Cite web
  9. Шаблон:Cite web
  10. Шаблон:Cite web
  11. Шаблон:Cite web
  12. Шаблон:Cite web
  13. Шаблон:Cite web
  14. Шаблон:Cite web
  15. Шаблон:Cite web
  16. Шаблон:Cite web
  17. 17,0 17,1 Шаблон:Cite web
  18. Шаблон:Cite web
  19. Шаблон:Cite web
  20. Шаблон:Cite web
  21. Шаблон:Cite web
  22. Шаблон:Cite web
  23. Шаблон:Cite web
  24. Шаблон:Cite web
  25. 25,0 25,1 Шаблон:Cite web
  26. Шаблон:Cite web
  27. Шаблон:Cite web
  28. Шаблон:Cite web
  29. Allowing static_assert(false)
  30. Шаблон:Cite web
  31. Шаблон:Cite web
  32. Шаблон:Cite web
  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. Шаблон:Cite web
  36. Шаблон:Cite web
  37. Шаблон:Cite web
  38. Шаблон:Cite web
  39. Шаблон:Cite web
  40. Шаблон:Cite web
  41. Шаблон:Cite web
  42. Шаблон:Cite web
  43. Шаблон:Cite web
  44. Шаблон:Cite web
  45. Шаблон:Cite web
  46. Шаблон:Cite web
  47. Шаблон:Cite web
  48. Шаблон:Cite web
  49. Шаблон:Cite web
  50. Шаблон:Cite web
  51. Шаблон:Cite web
  52. Шаблон:Cite web
  53. Шаблон:Cite web
  54. Шаблон:Cite web
  55. Шаблон:Cite web
  56. Шаблон:Cite web
  57. Шаблон:Cite web
  58. Шаблон:Cite web
  59. Шаблон:Cite web
  60. Шаблон:Cite web
  61. Шаблон:Cite web
  62. Шаблон:Cite web
  63. Шаблон:Cite web
  64. Шаблон:Cite web
  65. Шаблон:Cite web
  66. Шаблон:Cite web
  67. Шаблон:Cite web
  68. Шаблон:Cite web
  69. Шаблон:Cite web
  70. Шаблон:Wayback P1642R11: Freestanding Library: Easy [utilities], [ranges], and [iterators]
  71. Шаблон:Cite web
  72. 72,0 72,1 Шаблон:Cite web
  73. Шаблон:Cite web
  74. Шаблон:Cite web
  75. Шаблон:Cite web
  76. Шаблон:Cite web
  77. Шаблон:Cite web
  78. Шаблон:Cite web
  79. Шаблон:Cite web
  80. Шаблон:Cite web
  81. Шаблон:Cite web
  82. Шаблон:Cite web
  83. Шаблон:Cite web
  84. Шаблон:Cite web
  85. Шаблон:Cite web
  86. Шаблон:Cite web
  87. Шаблон:Cite web
  88. Шаблон:Cite web
  89. Шаблон:Cite web
  90. Шаблон:Cite web
  91. Шаблон:Cite web
  92. Шаблон:Cite web
  93. Шаблон:Cite web
  94. Шаблон:Cite web
  95. Шаблон:Cite web
  96. Шаблон:Cite web
  97. Шаблон:Cite web
  98. Шаблон:Cite web
  99. Шаблон:Cite web
  100. Шаблон:Cite web
  101. Шаблон:Cite web
  102. Шаблон:Cite web
  103. Шаблон:Cite web
  104. Шаблон:Cite web
  105. Шаблон:Cite web
  106. Шаблон:Cite web
  107. Шаблон:Cite web
  108. Шаблон:Cite web
  109. Шаблон:Cite web
  110. Шаблон:Cite web
  111. Шаблон:Cite web
  112. Шаблон:Cite web
  113. Шаблон:Cite web
  114. Шаблон:Cite web
  115. Шаблон:Cite web
  116. Шаблон:Cite web
  117. Шаблон:Cite web
  118. Шаблон:Cite web
  119. Шаблон:Cite web
  120. Шаблон:Cite web
  121. Шаблон:Cite web
  122. Шаблон:Cite web
  123. Шаблон:Cite web
  124. Шаблон:Cite web
  125. Шаблон:Cite web
  126. Шаблон:Cite web
  127. Шаблон:Cite web
  128. Шаблон:Cite web
  129. Шаблон:Cite web
  130. Шаблон:Cite web
  131. Шаблон:Cite web
  132. Шаблон:Cite web
  133. Шаблон:Cite web
  134. Шаблон:Cite web