C++23
один стандарт языка программирования Си++ Из Википедии, свободной энциклопедии
один стандарт языка программирования Си++ Из Википедии, свободной энциклопедии
C++23, на момент разработки C++2b — новый стандарт языка программирования C++. Из-за пандемии COVID все заседания комитета проходили полностью дистанционно (за исключением одного, частично дистанционного).
Стиль этой статьи неэнциклопедичен или нарушает нормы литературного русского языка. |
На июль 2023 закончен, но не прошёл через ISO. Макрос __cplusplus
поднят до 202302L
.
С новыми изменениями библиотеки минимальная программа выглядит так[1]
#include <print>
int main()
{
std::println("Hello, world!");
}
aligned_storage
, aligned_union
(Си++11) — чреваты ошибками, сложно объединять в более крупные конструкции. Замена — alignas
[2].numeric_limits::has_denorm
и has_denorm_loss
(Си++03) — поскольку поведение денормализованных чисел на аппаратном уровне (и даже при компиляции и исполнении) бывает разное, на это свойство нельзя полагаться; альтернатива разрабатывается[3]. Ранее аналогичные макросы запретили в Си.allocator_traits
(Си++03). Свойствами allocator (выделителя) можно управлять, выставляя его внутренние поля[4].for (using T = int; T e : v)
. В C++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].static_assert
и if constexpr
[11]: всё, кроме нуля, эквивалентно true
. В explicit/noexcept
, как и раньше, годятся только 0 и 1. Дробные числа, указатели и объекты без operator bool()
, как и раньше, запрещены.auto counter1 = [j=0]() mutable -> decltype(j) { return j++; };
auto lm = [][[nodiscard, vendor::attr]]()->int { return 42; };
Более раннее std::is_constant_evaluated()
, сделанное встроенной функцией компилятора, оказалось ошибкоопасным[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 или даже на 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;
}
}
Этот код вообще не компилировался, и хотелось бы сделать уголок в constexpr
-коде, который способен вызывать consteval
-функции.
Фигурные скобки в then-части обязательны, в else- могут опускаться. Писать вроде if (consteval && n < 0) {
невозможно, ради этого старую функцию не запретили.
Обратная форма выглядит как if not consteval {}
или if !consteval {}
.
Простой способ получить объект как временный, например[16]:
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}
.
Существующие методы[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;
Разные библиотеки реализуют недостающий синтаксис по-разному, но в любом случае это не сочетается с синтаксисом стандартных массивов, и затрудняет автоматический поиск ошибок и inline (развёртывание функции прямо в вызывающий код).
Предметом дискуссий остаются: нужно ли это для стандартных массивов; нужно ли ослабить требования к operator[]
и разрешить его за пределами класса.
Одна из возможностей Си++ — 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
означает, что есть хотя бы один путь исполнения, возможный при компиляции.optional.reset()
ещё не constexpr
(нововведение Си++20).Таким образом, теперь возможно написать constexpr-функцию, которая ни при одном наборе аргументов не сможет выполниться при компиляции[20]. Должен ли компилятор в таком случае выдавать предупреждения — не указано.
Также в constexpr-функциях разрешены goto, переменные нелитеральных типов, статические/внутрипоточные переменные. Если при компиляции будет пройдена любая из этих строк, функция вычисляется при выполнении[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 (¶m)[3]) {
int local[] = {1, 2, 3};
constexpr auto s0 = array_size(local); // OK
constexpr auto s1 = array_size(param); // Теперь OK
}
Разрешены static constexpr
-переменные в constexpr
-функциях[23]:
constexpr char xdigit(int n) {
static constexpr char digits[] = "0123456789abcdef";
return digits[n];
}
Это позволит, например, написать constexpr from_chars
[24].
Лямбда-функции и constexpr
-шаблоны могут вызывать consteval
и тогда сами становятся consteval
[25]. Для первой никак нельзя указать, что она consteval
. А шаблон теперь может стать условным consteval
в зависимости от пути инстанцирования.
operator()
и operator[]
Убирает одну машинную команду, если класс без данных и компилятор почему-то отказался от инлайнинга (добавления в вызывающий код вместо создания отдельной функции)[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 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) и расчёты с неопределённым поведением[28].
!=
и перевёрнутых ==
/!=
Чтобы писать меньше нетворческого кода, в С++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 и программист не написал операцию != сам. В некоторых случаях компилятор может запутаться и сказать: есть выбор между обычной и перевёрнутой операцией «равняется», и исправление этой ошибки простое — по старинке написать операцию «не равняется» самостоятельно.
У нового перечисления вариантов через if constexpr
нет одной черты, присутствовавшей у старых способов обеспечить принципиально разное поведение для разных типов (например, у перегрузок ostream << int
и ostream << const char*
): как вызвать ошибку, если ни одна ветвь не выполняется? static_assert(false)
не годился — компилятор выдавал ошибку, даже не инстанцируя; приходилось запутывать компилятор так, чтобы он не видел тождественный false
. Общепринятое решение — шаблон always_false<T>
, который пишут прямо на месте[30].
В С++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 (остальные).
Идентификатор должен быть нормализован по алгоритму «каноническая композиция» (NFC, разобрать монолитные символы на компоненты и собрать снова). Если нет — программа некорректна.
Это изменение только делает поддержку Юникода более целостной, но никак не решает вопросов атак через внешне одинаковые строки[31]. Методы передачи таких символов в линкер остаются за реализацией.
Разные компиляторы действовали по-разному на L'\U0001F926'
(эмодзи «фейспалм») на двухбайтовом wchar_t (Windows), L'ab'
. Теперь оба запрещены[32].
Многосимвольные char-литералы продолжают работать, имеют тип int. Сколько допускается символов и как они будут собраны в одно число — определяется реализацией.
Узаконено, что они могут отличаться[33], и wchar_t
— это единица широкой, зависящей от реализации кодировки исполнения[34]. Кодировка трансляции неизменна и покрывает весь Юникод, включая не существующие пока символы и несимвольные позиции, за исключением, возможно, половинок суррогатных пар. Кодировки файлов тоже бывают разные.
UTF-8 как кроссплатформенная кодировка файлов должна поддерживаться безусловно, всеми компиляторами[35]. Должен существовать флаг компилятора, объявляющий все файлы UTF-8 независимо от наличия в них метки порядка байтов Юникода. Метка не должна мешать компиляции; мало того, её наличие — допустимая тактика опознания юникодных файлов. В файле Юникода не должно быть некорректных кодовых комбинаций, однако могут быть корректные комбинации, соответствующие не существующим пока символам. Прочие принципы перекодировки задаются реализацией.
Раньше это было за реализацией, но оказалось, что основное назначение этой функции — определение кодировки исполнения[36]. Например, код из SQLite:
/* Проверка, использует ли машина EBCDIC.
(Да, верите или нет, ещё есть машины, использующие EBCDIC.) */
#if 'A' == '\301'
# define SQLITE_EBCDIC 1
#else
# define SQLITE_ASCII 1
#endif
Все крупные компиляторы фактически работают именно так.
Все три строчки сломаны в Си++20, снова работают в Си++23[37].
const char* a = u8"a";
const char b[] = u8"b";
const unsigned char c[] = u8"c";
Как оказалось, подобный слом требовал constexpr-преобразование char
↔char8_t
(reinterpret_cast
— не constexpr), мешал брать код на Си++14/17 и развёртывать на 20 (нередко нужны упомянутые преобразования), нарушал совместимость с Си.
"\u{1F926}"
для кодовой позиции Юникода, "\o{123}"
для восьмеричной системы и "\x{AB}"
для шестнадцатеричной[38].
Разрывать такие экранировки ("\x{4" "2}"
) запрещено.
"\N{LATIN CAPITAL LETTER A WITH MACRON}"
позволяет обратиться к символу по юникодному имени[39].
Исключает крокозябры в таком коде[40]:
std::locale::global(std::locale("Russian.1251"));
auto s = std::format("День недели: {}", std::chrono::Monday);
Набор возможных кодировок определяется реализацией. При неспособности — выбрасывается исключение.
Для каждого текста реализация сама определяет, сколько позиций терминала он займёт. Описана эталонная юникодная реализация: большинство восточноазиатских символов и эмодзи имеют ширину 2, все умляуты — 0, у остальных 1. Реализация локаленезависима. Ширина символа-заполнителя всегда принимается за 1[41].
Специальные числа вроде NaN и ∞ не заполняются нулями.
// Ширина эмодзи — 2
string sB = format("{:🤡^6}", "x"); // 🤡🤡x🤡🤡🤡
string sC = format("{:*^6}", "🤡🤡🤡"); // 🤡🤡🤡
double inf = numeric_limits<double>::infinity();
string s4 = format("{:06}", inf); // ␣␣␣inf
L"" u""
. Из крупных компиляторов такое поддерживает только SDCC — берёт первый из префиксов[44].#warning
, поддерживаемая всеми[45].span
и string_view
теперь TriviallyCopyable[48].static_assert
[49]. Однако составные конструкции, состоящие из static_assert
, для простоты экспортировать можно: export { static_assert(true); }
. (Например, в определённых настройках препроцессора в скобках остался один static_assert
.)[[no_unique_address]]
, и его игнорировать можно было и раньше. Остальные влияют на предупреждения или дают дополнительную информацию компилятору. А то, что влияет на программу, например alignas
,— не атрибут.В конструкции «for по объекту» говорится: ради безопасности и предсказуемости кода каждый временный объект, в нормальных условиях исчезающий в конце строки, сохраняется до конца цикла[51]. Исключением являются переданные по значению параметры функции — ведь они, исчезая как объекты в конце строки, вероятно, теряют своё содержимое раньше.
Это четвёртый случай, когда нарушено правило «деструкторы вызываются сразу же после окончания выражения». Остальные три: создание и копирование массива (ради простоты и эффективности для каждого элемента сначала исчезают временные, потом создаётся/копируется следующий), явная команда придержать объект через const int& x = 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[52].{ goto a; ++x; a: }
[53].<stdatomic.h>
. Аналога <cstdatomic>
нет[54].ios::noreplace
: файла не должно существовать. Это исключает перезапись нужного файла, исключает гонки за файл между двумя программами. Бит существовал во многих реализациях до Cи++98, и эквивалентен флагу x
Си11 (FILE* f = fopen("fname.ext", "wx");
) и отсутствию флага CREATE_ALWAYS в Win32. Например, может служить для поиска неиспользуемого временного файла[56].out_ptr
, inout_ptr
— адаптеры между умными указателями и Си-APIЭти обёртки призваны «подружить» «сырые» Си-API и умные указатели[57][58]. Исчезая, обёртка перезаписывает указатель. В любом случае в типе умного указателя программист должен верно указать, какой функцией объект уничтожать.
// Межъязыковой 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
[59].to_underlying
для преобразования enum → int
, более понятная по названию и менее ошибкоопасная[60].byteswap
в заголовке <bit>
для смены порядка байтов в числах[61].forward_like
— аналог forward
для объекта и его поля; понадобился из-за this-параметров. Означает «передать наподобие», и делает прямую передачу поля forward_like<This>(this->field)
[63].import std;
(всё пространство имён std::
) и import std.compat;
(функции совместимости вроде ::fopen
из стандартного пространства имён)[64]. На ноябрь 2022 модулей не поддерживает никто, но, по заявлениям «Открытых систем», даже компиляция Hello World серьёзно ускорилась[65].equality_comparable_with
и другие, чтобы поддерживали некопируемые типы[66].mdspan
— нехранящий многомерный массив[68]visit
для работы с типами, унаследованными от variant
(обычно какие-то реализации конечных автоматов)[69].common_reference_t
— всегда ссылка, а не reference_wrapper
[70].<utility>
внесён полностью, а <ranges>
и <iterator>
— частично[71].invoke_r
, используемое при сложной композиции шаблонов[72].time_point::clock
, чтобы можно было налаживать разные виды часов, в том числе хранящие некое состояние (например, синхронизирующиеся по интернету через NTP)[73].stacktrace
— информация о вложенности вызововОдно из важнейших нововведений Си++23, позволяющее видеть ошибку вернее, чем короткие сообщения самопроверок[74]. Например: если случился выход за пределы массива, самопроверка скажет: обращался к 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
придумали стандартную функцию форматирования[75].
Как связывать stacktrace с выпадающими авариями — пока не придумали, ведь то и другое — довольно тяжёлые части языка и библиотеки.
move_only_function
— облегчённая некопируемая обёртка для объектов-функцийФункциональное программирование в Си++ обычно (например, в STL) сделано через шаблоны. Но шаблоны не всегда годятся: вызываемая функция бывает большая, или в шаблонном виде выставляет наружу много лишнего, или принципиально не шаблонная (виртуальная). Да и нешаблонную функцию проще отлаживать. В Си++11 это частично решено обёрткой std::function
.
std::function
стал одной из самых «тяжёлых» частей библиотеки STL. Избавившись от нескольких возможностей — нельзя копировать, отсутствуют поля target
и target_type
— можно получить значительно более лёгкий[76] объект[77]. Этот объект может работать с некопируемыми перехватами.
Объект блокирующей межпоточной синхронизации «барьер» используется для частично распараллеливаемых задач: как только каждый поток выполнит свою долю и все соберутся у барьера, выполняется координационная функция и решает, что делать дальше. В реализации для Си++ поток-координатор и ожидающие очередного этапа вычислений потоки-клиенты должны вызвать wait
, а потоки-работники — arrive
при обычном исполнении и arrive_and_drop
, если поток выходит из параллельного вычисления.
Координационная функция теперь может выполниться в любом потоке: не только в последнем arrive
, но и в wait
[78]. Что будет, если никто не вызовет wait
(то есть выделенного координатора нет),— зависит от реализации. Связано с разнородным аппаратным обеспечением — координатор и клиенты работают на одной архитектуре, а потоки-работники на другой.
expected
— результат или код ошибкиУ обработки ошибок есть четыре важных свойства:
Главный недостаток исключений — незаметность. У кодов ошибок как минимум грязный код, и они забивают важный канал — возвращаемое значение[79].
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
обзавелись похожими функциями.
Монада — стандартная возможность функциональных языков произвести последовательность действий.
В математике последовательность функций записывается как , что не всегда удобно — в программировании часто лучше x.f().g().h()
.
std::optional
— довольно простая обёртка, смысл которой — хранить объект или ничего. Проверки на «ничего» занимают немалую часть работы с optional — а что, если в процессе преобразований картинки на ней не окажется кота? А что, если нет места, куда пририсовать бантик?[80]
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
[81].
Существовал 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);
[82].
Впоследствии уточнили, что print
синхронизирован с другими методами вывода в консоль[83].
Название | Битов мантиссы/ порядка | Диапазон | 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 только подразумевается и не хранится.
Математические функции должны иметь обёртки для всех поддерживаемых типов — при этом реальный расчёт может вестись в более или менее точном типе[84].
view
удалена инициализация без параметров[85].zip
для параллельного прохождения разных диапазонов[90].ranges::to
— преобразование из диапазона в контейнер[91].iota
, shift_left
, shift_right
[92].chunk
и slide
[93].chunk_by
[94].join_with
[95].
join[_with]
с итераторами, которые возвращают ссылку на что-то внутри самого итератора[96].starts_with
, ends_with
[97].split_view
переименован в lazy_split_view
, добавлен новый split_view
[98].join_view
[99].string_view
можно строить из непрерывного диапазона[100]. Впоследствии уточнили: конструктор явный (explicit)[101].format
[102].format
— вывод запрещён, как отображение (map), как множество (set), как кортеж (sequence), как строку (string), как отладочное сообщение (debug_string).[103].generator
— синхронный генератор диапазонов на сопрограммах[104]. Налажено форматирование подобных объектов[105]. Первое использование сопрограмм в стандартной библиотеке.single_view
и другие адаптеры-представления теперь могут работать с перемещаемыми, но не копируемыми типами[106].views::repeat
, повторяющая один объект N раз или до бесконечности[107].cartesian_product
— декартово произведение диапазонов[109][110].ranges::fold
, представляющая собой f(f(…f(f(init, x1), x2), …), xn)[111].ranges::contains
, ranges::contains_subrange
[112].stride_view
, функция views::stride
— представление с шагом N[113]ranges::find_last()
, find_last_if()
, find_last_if_not
[114].views::enumerate
[115] — часто функцией Си++ «проход по контейнеру» не пользовались просто потому, что вдобавок требовался номер в последовательности.function
— теперь будет компилироваться std::function f = less<int>{};
, если less — шаблон с новой статической операцией «вызов»[26].function
и packaged_task
— теперь будет компилироваться std::function g = F{};
, если F — объект, чья операция «вызов» содержит новый (также ожидаемый в Си++23) this-параметр[116]string[_view].contains
— часто надо проверить на наличие подстроки, не выясняя, где совпадение[117].string.substr() &&
(с временным this
) — для оптимизации auto a = someTemporaryString().substr(0, 2);
[118].string[_view](nullptr_t) = delete
— как подсказка: строить строку из пустого указателя запрещено.Используется для экстремальной оптимизации на стыке строк и низкоуровневых 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
?[17] Дело в том, что стоимость выделения памяти мало зависит от длины буфера, и в большинстве случаев на буфер будет выделено значительно больше памяти, чем реально потребуется на сжатую строку. Новая функция не инициализирует буфер, и ушло зануление очень длинного участка памяти — memset( compressed.data(), '\0', compressed.size())
.
move_iterator
— теперь итератор того же вида (односторонний/двусторонний/произвольного доступа), что и исходный итератор (раньше только односторонний)[119].cbegin
всегда должен возвращать константный итератор[120].iterator_category
в counted_iterator
и некоторых других, чтобы из них можно было собирать сложные диапазоны[121].stack/queue
, принимающие пару итераторов[123].pair
даны типы по умолчанию[124].extract
и erase
в ассоциативных контейнерах[125]. Например, ключ хранения string
, а ключ доступа — string_view
.Allocator.allocate_at_least
, более полно использующая особенности механизма выделения памяти[127]. Контейнеры переменного размера понемногу будут переходить на неё.flat_[multi]set
и flat_[multi]map
, работающие как минимум на шаблонах vector
, deque
, list
. Представляют собой простые сортированные массивы.Помимо стандартного форматирования новых объектов (описано в соответствующих разделах), там есть:
thread::id
[75].format_to
, если обработка при компиляции удастся[134].format_string.get()
. Автоматическая проверка корректности format
при компиляции ограничивается функцией format
, и пока обходной способ добавить проверку в свою функцию (например, log<Args...>(format_string, Args...)
— писать собственные реализации format_string
, пользуясь по максимуму штатными библиотеками[135]. Для последнего и нужен get
. Предполагается, что в новых версиях проверка будет сделана чище — например, через проверку параметров при компиляции.exchange
получил условный noexcept — если объект создаётся с перемещением и присваивается (с перемещением или по копии, в зависимости от правого параметра), не вызывая исключений[136].apply
получил такой же условный noexcept[137].malloc
и bit_cast
ещё с Си++20 получили прозвище «благословенные» — они могут неявно создавать объекты. То есть: не оперируя типом X, тем не менее, подразумевают, что в памяти может появиться объект типа X. Теперь можно «благословить» любую функцию кодом X* p = std::start_lifetime_as<X>(myMalloc(sizeof(struct X));
— и подобные вызовы не будут неопределённым поведением[138].
std::is_implicit_lifetime
[139].reference_constructs_from_temporary
, reference_converts_from_temporary
. Некоторые объекты (std::tuple<const std::string&>
) теперь не могут конструироваться из подобных объектов[140].string[_view](nullptr_t) = delete
— как подсказка: строить строку из пустого указателя запрещено.Функция, указывающая, что при нормальной работе кода в данную точку попасть нельзя[141]. Поведение неопределённое, вызов этой функции в действующем коде — всегда нештатная ситуация.
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]]
с пустым телом.
Предложение 1 апреля 2021 отсылало на My Little Pony, введя два ключевых слова «my» и «little», и объект «pony» — волшебный объект, делающий что угодно[142]. Кроме того, высмеивало программистский жаргон:
#embed
— загрузка массива из двоичного файла.static_assert
и других).auto [a, b [[vendor::attribute]], c] = f();
inspect
— более мощная версия switch
.format
соответствуют друг другу, в Си++20 применяются сложные шаблоны. Поддержка со стороны языка упростила бы это.Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.