Interested Article - C++23

C++23 , на момент разработки C++2b — новый стандарт языка программирования C++ . Из-за эпидемии COVID все заседания комитета проходили полностью дистанционно (за исключением одного, частично дистанционного).

На июль 2023 закончен, но не прошёл через ISO. Макрос __cplusplus поднят до 202302L .

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

Удалены

Запрещены

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

Снят запрет

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

Язык

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

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

if consteval

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

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 или даже на AVX!");
    }
}

Конечно, компиляторы выдают предупреждение, но неочевидно, что делать — правильно 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 ) { невозможно, ради этого старую функцию не запретили.

Обратная форма выглядит как if not consteval {} или if ! consteval {} .

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

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

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

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

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

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

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

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

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

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

This-параметры

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

///// БЫЛО /////
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, и т. д. Аргументация:

  • С Си++20 слово constexpr означает, что есть хотя бы один путь исполнения, возможный при компиляции.
  • Библиотеки всегда отстают от языка: компилятор уже поддерживает Си++23, а optional . reset () ещё не constexpr (нововведение Си++20).

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

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

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

// Самодельный 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 -функциях :

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

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

Лямбда-функции и constexpr -шаблоны могут вызывать consteval и тогда сами становятся consteval . Для первой никак нельзя указать, что она consteval . А шаблон теперь может стать условным consteval в зависимости от пути инстанцирования.

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

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

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

Аннотация [[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 13.2 -std=c++23 -O3
; Без assume — знаковое деление на 32 без «тяжёлых» операций: ветвлений и div
test    edi, edi       ; рассчитать процессорные флаги (нам важен sf) для edi≡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       ; требуется по соглашению вызова: параметр в rdi, результат в rax
sar     eax, 5         ; битовый сдвиг
ret

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

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

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

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)

У нового перечисления вариантов через if constexpr нет одной черты, присутствовавшей у старых способов обеспечить принципиально разное поведение (например, у перегрузок ostream << int и ostream << const char * ): как вызвать ошибку, если ни одна ветвь не выполняется ? static_assert ( false ) не годился — компилятор выдавал ошибку, даже не инстанцируя ; приходилось запутывать компилятор так, чтобы он не видел тождественный false . Общепринятое решение — шаблон always_false < T > , который пишут прямо на месте .

В С++23 static_assert в шаблонах проверяется при инстанцировании, даже если выражение можно вычислить, не инстанцируя . Если он должен проверяться всегда — вытащите его за пределы шаблона.

// Больше не нужно
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, разобрать монолитные символы на компоненты и собрать снова). Если нет — программа некорректна.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Исключает в таком коде :

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

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

Уточнены принципы расчёта ширины в консоли

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

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

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

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

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

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

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

Это четвёртый случай, когда нарушено правило «деструкторы вызываются сразу же после окончания выражения». Остальные три: создание и копирование массива (ради простоты и эффективности для каждого элемента сначала исчезают временные, потом создаётся/копируется следующий), явная команда придержать объект через const int & b = static_cast < const int &> ( 0 ); .

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

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

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

Обёртки out_ptr , inout_ptr

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

// Межъязыковой 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) {
	// обработка ошибок
}

Библиотека

Разные изменения в библиотеке

  • Семейство констант is_scoped_enum — например, для отслеживания миграции библиотеки со старых enum на новые enum class .
  • Функция to_underlying для преобразования enum int , более понятная по названию и менее ошибкоопасная .
  • Функция byteswap в заголовке < bit > для смены порядка байтов в числах .
  • iostream теперь может печатать volatile-указатели — точно так же, как и обычные .
  • forward_like — аналог forward для объекта и его поля; понадобился из-за this-параметров .
  • Зарезервированы два модуля: import std ; (всё пространство имён std :: ) и import std . compat ; (функции совместимости вроде :: fopen из стандартного пространства имён) . На ноябрь 2022 модулей не поддерживает никто, но, по заявлениям « Открытых систем », даже компиляция Hello World серьёзно ускорилась .
  • Переписаны концепции equality_comparable_with и другие, чтобы поддерживали некопируемые типы .
  • Больше совместимости между кортежами ( tuple ) и кортежеподобными объектами (парами, статическими массивами) .
  • mdspan — нехранящий многомерный массив
  • Усовершенствован visit для работы с типами, унаследованными от variant (обычно какие-то реализации конечных автоматов ) .
  • Уточнено, что common_reference_t — всегда ссылка, а не reference_wrapper .
  • У библиотеки изначально были два варианта: автономная (freestanding) и платформенная (hosted) ; автономная не содержит системных вызовов, может писаться даже на чистом Си++, и будет работать даже на нестандартных машинах без консоли и файловой системы. В автономную версию < utility > внесён полностью, а < ranges > и < iterator > — частично .
  • invoke_r , используемое при сложной композиции шаблонов .
  • Ослаблены требования к функции time_point :: clock , чтобы можно было налаживать разные виды часов, в том числе хранящие некое состояние (например, синхронизирующиеся по интернету через NTP ) .

stacktrace

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

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

move_only_function

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

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

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

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

expected

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

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

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

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 — а что, если в процессе преобразований картинки на ней не окажется кота? А что, если нет места, куда пририсовать бантик?

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 .

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

Существовал strstream поток данных , работающий на массиве ограниченной длины. Из-за угрозы переполнений запрещён уже в C++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 ); .

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

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

Название Битов мантиссы/
​порядка
Диапазон ulp(1.0) Примечание
float16_t 10+5 6.1e-5..65504 9.8e-4 Соответствует IEEE binary16
bfloat16_t 7+8 1.2e-38..3.4e38 7.8e-3 Верхние два байта IEEE binary32 (≈float), используется в ИИ-библиотеках, отсюда имя — brain float
float32_t 23+8 1.2e-38..3.4e38 1.2e-7 Соответствует IEEE binary32, большинству реализаций float
float64_t 52+11 2.2e-308..1.7e308 2.2e-16 Соответствует IEEE binary64, большинству реализаций double
float128_t 112+15 3.4e-4932..1.1e4932 1.9e-34 Соответствует IEEE binary128

У всех этих типов неявная единица : целая часть мантиссы 1,xxx только подразумевается и не хранится.

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

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

  • Из концепции view удалена инициализация без параметров .
  • Уточнены требования к дипазонам , представлениям , адаптерам диапазонов .
  • Механизмы для написания собственных адаптеров диапазонов
  • Семейство адаптеров zip для параллельного прохождения разных диапазонов .
  • ranges :: to — преобразование из диапазона в контейнер .
  • Функции iota , shift_left , shift_right .
  • Функции chunk и slide .
  • Функция chunk_by .
  • Функция join_with .
    • Улучшена работа join [ _with ] с итераторами, которые возвращают ссылку на что-то внутри самого итератора .
  • Функции starts_with , ends_with .
  • split_view переименован в lazy_split_view , добавлен новый split_view .
  • Ослаблены ограничения на join_view .
  • string_view можно строить из непрерывного диапазона . Впоследствии уточнили: конструктор явный ( explicit ) .
  • Механизмы вывода диапазонов функцией format .
  • Добавлены механизмы указания, как печатать нестандартный диапазон функцией format — вывод запрещён, как отображение ( map ), как множество ( set ), как кортеж ( sequence ), как строку ( string ), как отладочное сообщение ( debug_string ). .
  • generator — синхронный генератор диапазонов на сопрограммах . Налажено форматирование подобных объектов .
  • single_view и другие адаптеры-представления теперь могут работать с перемещаемыми, но не копируемыми типами .
  • Новая функция-адаптер views :: repeat , повторяющая один объект N раз или до бесконечности .
  • Переписаны требования к алгоритмам, чтобы они могли пользоваться итераторами диапазонов .
  • Функция cartesian_product декартово произведение диапазонов .
  • Функция ranges :: fold , представляющая собой f(f(…f(f(init, x 1 ), x 2 ), …), x n ) .
  • Функции ranges :: contains , ranges :: contains_subrange .
  • Объект stride_view , функция views :: stride — представление с шагом N
  • Функции ranges :: find_last () , find_last_if () , find_last_if_not .
  • Функция views :: enumerate — часто функцией Си++ «проход по контейнеру» не пользовались просто потому, что вдобавок требовался номер в последовательности.

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

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

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

  • string [ _view ]. contains — часто надо проверить на наличие подстроки, не выясняя, где совпадение .
  • string . substr () && (с временным this ) — для оптимизации auto a = someTemporaryString (). substr ( 0 , 2 ); .
  • 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 ? Дело в том, что стоимость выделения памяти мало зависит от длины буфера, и в большинстве случаев на буфер будет выделено значительно больше памяти, чем реально потребуется на сжатую строку. Новая функция не инициализирует буфер, и ушло зануление очень длинного участка памяти — memset ( compressed . data (), '\0' , compressed . size ()) .

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

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

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

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

Новые constexpr

  • Больше constexpr в bitset .
  • Целочисленные from_chars , to_chars .
  • Почти все функции unique_ptr — после того, как new и delete стали выполняться в этом контексте.
  • Математика из cmath , cstdlib .
  • Большинство функций optional , variant .
  • type_info :: op ==

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

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

  • Форматирование thread :: id .
  • Проверка корректности форматирования при компиляции, если такое возможно; облегчение format_to , если обработка при компиляции удастся .
  • Открыт format_string . get () . Автоматическая проверка корректности format при компиляции ограничивается функцией format , и пока обходной способ добавить проверку в свою функцию (например, log < Args ... > ( format_string , Args ...) — писать собственные реализации format_string , пользуясь по максимуму штатными библиотеками . Для последнего и нужен get . Предполагается, что в новых версиях проверка будет сделана чище — например, через проверку параметров при компиляции.

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

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

Функция unreachable

Функция, указывающая, что при нормальной работе кода в данную точку попасть нельзя .

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

В большинстве компиляторов она «волшебная» (реализованная внутри компилятора), и в GCC/CLang много лет существовала под названием __builtin_unreachable . Но даже без «волшебства» подходящая реализация простейшая: [[ noreturn ]] с пустым телом.

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

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

Примечания

  1. . Дата обращения: 8 августа 2022. 18 июля 2022 года.
  2. . Дата обращения: 9 марта 2023. 2 апреля 2023 года.
  3. . Дата обращения: 9 марта 2023. 9 марта 2023 года.
  4. . Дата обращения: 3 апреля 2023. 2 апреля 2023 года.
  5. Дата обращения: 9 августа 2022. 9 августа 2022 года.
  6. . Дата обращения: 27 июля 2022. 24 мая 2022 года.
  7. . Дата обращения: 27 июля 2022. 27 июля 2022 года.
  8. . Дата обращения: 27 июля 2022. 27 июля 2022 года.
  9. . Дата обращения: 27 июля 2022. 22 августа 2022 года.
  10. . Дата обращения: 1 августа 2022. 30 июля 2022 года.
  11. . Дата обращения: 20 июля 2022. 20 июля 2022 года.
  12. . www.open-std.org . Дата обращения: 7 апреля 2023. 7 апреля 2023 года.
  13. . Дата обращения: 28 июля 2022. 14 мая 2022 года.
  14. . Дата обращения: 20 июля 2022. 24 мая 2022 года.
  15. . Дата обращения: 27 июля 2022. 12 июля 2022 года.
  16. . Дата обращения: 29 июля 2022. 25 июля 2022 года.
  17. . Дата обращения: 20 июля 2022. 24 мая 2022 года.
  18. . Дата обращения: 7 ноября 2022. 3 ноября 2022 года.
  19. . Дата обращения: 19 декабря 2022. 19 декабря 2022 года.
  20. . Дата обращения: 8 марта 2023. 8 марта 2023 года.
  21. . Дата обращения: 19 декабря 2022. 19 декабря 2022 года.
  22. . Дата обращения: 29 июля 2022. 29 июля 2022 года.
  23. . Дата обращения: 19 декабря 2022. 19 декабря 2022 года.
  24. . Дата обращения: 6 ноября 2022. 6 ноября 2022 года.
  25. . Дата обращения: 7 ноября 2022. 7 ноября 2022 года.
  26. . Дата обращения: 30 июня 2023. 9 июня 2023 года.
  27. . Дата обращения: 27 июля 2022. 12 июля 2022 года.
  28. . Дата обращения: 27 июля 2022. 27 июля 2022 года.
  29. . Дата обращения: 27 июля 2022. 24 мая 2022 года.
  30. . Дата обращения: 27 июля 2022. 24 мая 2022 года.
  31. . Дата обращения: 19 декабря 2022. 11 декабря 2022 года.
  32. . Дата обращения: 29 июля 2022. 24 мая 2022 года.
  33. . Дата обращения: 29 июля 2022. 29 июля 2022 года.
  34. . Дата обращения: 23 января 2023. 23 января 2023 года.
  35. . Дата обращения: 14 апреля 2023. 14 апреля 2023 года.
  36. . Дата обращения: 27 июля 2022. 24 мая 2022 года.
  37. . Дата обращения: 27 июля 2022. 24 мая 2022 года.
  38. . Дата обращения: 27 июля 2022. 27 июля 2022 года.
  39. . Дата обращения: 27 июля 2022. 30 июля 2022 года.
  40. . Дата обращения: 1 августа 2022. 24 мая 2022 года.
  41. . Дата обращения: 5 октября 2022. 5 октября 2022 года.
  42. . Дата обращения: 20 ноября 2022. 1 декабря 2022 года.
  43. . Дата обращения: 19 декабря 2022. 19 декабря 2022 года.
  44. . Дата обращения: 21 марта 2023. 15 марта 2023 года.
  45. . Дата обращения: 19 декабря 2022. 19 декабря 2022 года.
  46. . Дата обращения: 20 июля 2022. 10 июня 2022 года.
  47. . Дата обращения: 29 июля 2022. 17 июня 2022 года.
  48. . Дата обращения: 8 августа 2022. 8 августа 2022 года.
  49. . Дата обращения: 5 октября 2022. 8 октября 2022 года.
  50. . Дата обращения: 19 ноября 2022. 30 ноября 2022 года.
  51. . Дата обращения: 1 мая 2023. 4 мая 2023 года.
  52. . Дата обращения: 1 мая 2023. 1 мая 2023 года.
  53. . Дата обращения: 8 августа 2022. 24 мая 2022 года.
  54. . Дата обращения: 8 августа 2022. 8 августа 2022 года.
  55. . Дата обращения: 8 августа 2022. 8 августа 2022 года.
  56. . Дата обращения: 5 октября 2022. 8 октября 2022 года.
  57. . Дата обращения: 19 ноября 2022. 30 ноября 2022 года.
  58. . Дата обращения: 19 ноября 2022. 18 ноября 2022 года.
  59. . Дата обращения: 3 апреля 2023. 7 апреля 2023 года.
  60. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  61. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  62. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  63. . Дата обращения: 21 ноября 2022. 21 ноября 2022 года.
  64. . Дата обращения: 9 марта 2023. 9 марта 2023 года.
  65. от 4 мая 2023 на Wayback Machine P1642R11: Freestanding Library: Easy [utilities ] , [ranges ] , and [iterators ]
  66. . Дата обращения: 22 ноября 2023. 22 ноября 2023 года.
  67. . Дата обращения: 22 ноября 2023. 22 ноября 2023 года.
  68. . Дата обращения: 21 ноября 2022. 21 ноября 2022 года.
  69. . Дата обращения: 9 марта 2023. 8 марта 2023 года.
  70. . Дата обращения: 7 ноября 2022. 7 ноября 2022 года.
  71. . Дата обращения: 20 июля 2022. 20 июля 2022 года.
  72. . Дата обращения: 9 марта 2023. 9 марта 2023 года.
  73. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  74. . Дата обращения: 20 июля 2022. 20 июля 2022 года.
  75. . Дата обращения: 19 ноября 2022. 17 ноября 2022 года.
  76. . Дата обращения: 29 июля 2022. 24 июля 2022 года.
  77. Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  78. . Дата обращения: 29 июля 2022. 29 июля 2022 года.
  79. . Дата обращения: 13 ноября 2022. 13 ноября 2022 года.
  80. . Дата обращения: 19 ноября 2022. 7 апреля 2023 года.
  81. Дата обращения: 13 ноября 2022. 13 ноября 2022 года.
  82. . Дата обращения: 21 ноября 2022. 21 ноября 2022 года.
  83. . Дата обращения: 13 ноября 2022. 13 ноября 2022 года.
  84. . Дата обращения: 13 ноября 2022. 13 ноября 2022 года.
  85. . Дата обращения: 13 ноября 2022. 10 декабря 2022 года.
  86. . Дата обращения: 13 ноября 2022. 13 ноября 2022 года.
  87. . Дата обращения: 13 ноября 2022. 13 ноября 2022 года.
  88. . Дата обращения: 13 ноября 2022. 13 ноября 2022 года.
  89. . Дата обращения: 13 ноября 2022. 1 декабря 2022 года.
  90. . Дата обращения: 9 марта 2023. 9 марта 2023 года.
  91. . Дата обращения: 13 ноября 2022. 13 ноября 2022 года.
  92. . Дата обращения: 13 ноября 2022. 13 ноября 2022 года.
  93. . Дата обращения: 13 ноября 2022. 13 ноября 2022 года.
  94. . Дата обращения: 13 ноября 2022. 5 октября 2022 года.
  95. . Дата обращения: 19 ноября 2022. 9 декабря 2022 года.
  96. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  97. . Дата обращения: 19 ноября 2022. 30 ноября 2022 года.
  98. . Дата обращения: 19 ноября 2022. 30 ноября 2022 года.
  99. . Дата обращения: 21 ноября 2022. 21 ноября 2022 года.
  100. . Дата обращения: 19 ноября 2022. 30 ноября 2022 года.
  101. . Дата обращения: 19 ноября 2022. 30 ноября 2022 года.
  102. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  103. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  104. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  105. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  106. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  107. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  108. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  109. . Дата обращения: 9 марта 2023. 8 марта 2023 года.
  110. . Дата обращения: 19 ноября 2022. 1 декабря 2022 года.
  111. . Дата обращения: 8 августа 2022. 8 августа 2022 года.
  112. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  113. . Дата обращения: 19 ноября 2022. 27 ноября 2022 года.
  114. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  115. . Дата обращения: 21 ноября 2022. 21 ноября 2022 года.
  116. . Дата обращения: 21 ноября 2022. 21 ноября 2022 года.
  117. . Дата обращения: 22 ноября 2023. 1 октября 2023 года.
  118. . Дата обращения: 22 ноября 2023. 1 ноября 2023 года.
  119. . Дата обращения: 29 июля 2022. 24 мая 2022 года.
  120. . Дата обращения: 22 ноября 2023. 16 января 2024 года.
  121. . Дата обращения: 8 августа 2022. 20 июля 2022 года.
  122. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  123. . Дата обращения: 20 ноября 2022. 20 ноября 2022 года.
  124. . Дата обращения: 20 ноября 2022. 5 октября 2022 года.
  125. . Дата обращения: 20 ноября 2022. 30 ноября 2022 года.
  126. . Дата обращения: 21 ноября 2022. 21 ноября 2022 года.
  127. . Дата обращения: 22 ноября 2023. 22 ноября 2023 года.
  128. . Дата обращения: 21 ноября 2022. 22 октября 2022 года.
  129. . Дата обращения: 19 ноября 2022. 27 ноября 2022 года.
  130. . Дата обращения: 28 июля 2022. 28 июля 2022 года.
  131. . Дата обращения: 19 ноября 2022. 20 ноября 2022 года.
  132. . Дата обращения: 19 ноября 2022. 9 декабря 2022 года.
  133. . Дата обращения: 9 марта 2023. 9 марта 2023 года.
  134. . Дата обращения: 20 ноября 2022. 5 декабря 2022 года.
  135. . Дата обращения: 20 ноября 2022. 1 декабря 2022 года.
Источник —

Same as C++23