C++23
C++23, на момент разработки C++2b — новый стандарт языка программирования C++. Из-за пандемии COVID все заседания комитета проходили полностью дистанционно (за исключением одного, частично дистанционного).
Принят ISO с большим опозданием в октябре 2024[1]. Макрос Шаблон:Cpp поднят до Шаблон:Cpp.
Новый Hello world
С новыми изменениями библиотеки минимальная программа выглядит так[2]
#include <print>
int main()
{
std::println("Hello, world!");
}
Запрещены и удалены
Удалены
- Сбор мусора (Си++11) — ни одна из двух задач, доработать Си++ для сред со сбором мусора (.NET, Android…) и включить такую среду в стандартную библиотеку Си++ (наряду с обычным полуавтоматическим управлением памятью), не была выполнена.
Запрещены
- Шаблон:Cpp, Шаблон:Cpp (Си++11) — чреваты ошибками, сложно объединять в более крупные конструкции. Замена — Шаблон:Cpp[3].
- Свойства дробного Шаблон:Cpp и Шаблон:Cpp (Си++03) — поскольку поведение денормализованных чисел на аппаратном уровне (и даже при компиляции и исполнении) бывает разное, на это свойство нельзя полагаться; альтернатива разрабатывается[4]. Ранее аналогичные макросы запретили в Си.
- Запрещается делать свои Шаблон:Cpp (Си++03). Свойствами allocator (выделителя) можно управлять, выставляя его внутренние поля[5].
Снят запрет
- Операции Шаблон:Cpp и другие с Шаблон:Cpp-переменными (запрещены в Си++20). В микроконтроллерах часто используются многобитные порты, спроецированные в память[6], потому разрешили побитовые операции Шаблон:Cpp, Шаблон:Cpp, Шаблон:Cpp и др.[7].
- Присваивание char[] ← UTF-8 Шаблон:Cpp (нарушено в C++20, см. ниже).
- Операция «запятая» в квадратных скобках, со сменой значения — многомерная операция индексирования (см. ниже).
Язык
Мелкие изменения
- Предварительный оператор if/цикла может быть using-псевдонимом: Шаблон:Cpp. В C++20 работало Шаблон:Cpp, ведь синтаксически typedef — это определение переменной[8].
- Разрешён код Шаблон:Cpp — auto теперь позволяет указатели и ссылки на массивы[9].
- Новый литеральный суффикс Шаблон:Cpp (знаковый эквивалент Шаблон:Cpp), Шаблон:Cpp (Шаблон:Cpp).
- В лямбда-функции без параметров допустимо опускать круглые скобки, если есть ключевое слово Шаблон:Cpp и другие подобные: Шаблон:Cpp[10].
- Разрешены повторы атрибутов[11] — оказалось, они часто генерируются макросами.
- Расширено неявное преобразование int→bool в Шаблон:Cpp и Шаблон:Cpp[12]: всё, кроме нуля, эквивалентно Шаблон:Cpp. В Шаблон:Cpp, как и раньше, годятся только 0 и 1. Дробные числа, указатели и объекты без Шаблон:Cpp, как и раньше, запрещены.
- Хвостовой возвращаемый тип в лямбда-функциях сначала смотрит в перехваты, и только потом — в окружающий текст[13]: Шаблон:Cpp
- Разрешены атрибуты у лямбда-функций[14]: Шаблон:Cpp
- При наследовании конструктора наследуются и подсказки по автоопределению параметров шаблона (deduction guides)[15].
if consteval
Более раннее Шаблон:Cpp, сделанное встроенной функцией компилятора, оказалось ошибкоопасным[16]. Например:
constexpr size_t strlen(char const* s) {
//if constexpr (std::is_constant_evaluated()) { Было, не вызывало ассемблерную версию
if consteval { // Стало
for (const char *p = s; ; ++p) {
if (*p == '\0') {
return static_cast<std::size_t>(p - s);
}
}
} else {
__asm__("Нечто оптимизированное на SSE или даже на AVX!");
}
}
Конечно, компиляторы выдают предупреждение, но неочевидно, что делать — правильно Шаблон: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;
}
}
Этот код вообще не компилировался, и хотелось бы сделать уголок в Шаблон:Cpp-коде, который способен вызывать Шаблон:Cpp-функции.
Фигурные скобки в then-части обязательны, в else- могут опускаться. Писать вроде Шаблон:Cpp невозможно, ради этого старую функцию не запретили.
Обратная форма выглядит как Шаблон:Cpp или Шаблон:Cpp.
auto(x) — временная копия объекта
Простой способ получить объект как временный, например[17]:
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.
Многомерная операция индексирования (квадратные скобки)
Существующие методы[18]:
array(1, 2, 3, 4, 5) = 42; // выглядит ужасно, если вы не учили FORTRAN
array[{1, 2, 3, 4, 5}] = 42; // очень непонятно и неприятно писать
array[1][2][3][4][5] = 42; // чуть лучше, но под капотом творится просто жуть
Пока только для пользовательских типов[19].
int buffer[2*3*4] = { };
auto s = std::mdspan<int, std::extents<2, 3, 4>> (buffer);
s[1, 1, 1] = 42;
Разные библиотеки реализуют недостающий синтаксис по-разному, но в любом случае это не сочетается с синтаксисом стандартных массивов, и затрудняет автоматический поиск ошибок и inline (развёртывание функции прямо в вызывающий код).
Предметом дискуссий остаются: нужно ли это для стандартных массивов; нужно ли ослабить требования к Шаблон:Cpp и разрешить его за пределами класса.
This-параметры
Одна из возможностей Си++ — const-корректность — приводит к дублированию кода или написанию способов делегирования. Предлагается решение этого через шаблоны[20]
///// БЫЛО /////
class TextBlock {
public:
char const& operator[](size_t position) const {
// ...
return text[position];
}
char& operator[](size_t position) {
return const_cast<char&>(
static_cast<TextBlock const&>
(this)[position]
);
}
// ...
};
///// СТАЛО /////
class TextBlock {
public:
template <typename Self>
auto& operator[](this Self&& self, size_t position) {
// ...
return self.text[position];
}
// ...
};
Методы-расширения пока не предлагаются, но будут возможны в дальнейшем.
Другое назначение — повышение производительности при работе с классами минимального размера: даже если компилятор не умеет оптимизировать, будет происходить копирование, а не передача по ссылке.
Снижение требований к constexpr
Сделано множество послаблений: сигнатура не обязательно состоит из литеральных типов, подобъекты не обязательно конструируются/уничтожаются через constexpr, и т. д. Аргументация:
- С Си++20 слово Шаблон:Cpp означает, что есть хотя бы один путь исполнения, возможный при компиляции.
- Библиотеки всегда отстают от языка: компилятор уже поддерживает Си++23, а Шаблон:Cpp ещё не Шаблон:Cpp (нововведение Си++20).
Таким образом, теперь возможно написать constexpr-функцию, которая ни при одном наборе аргументов не сможет выполниться при компиляции[21]. Должен ли компилятор в таком случае выдавать предупреждения — не указано.
Также в constexpr-функциях разрешены goto, переменные нелитеральных типов, статические/внутрипоточные переменные. Если при компиляции будет пройдена любая из этих строк, функция вычисляется при выполнении[22].
В constexpr-функциях теперь могут участвовать указатели и ссылки с неизвестным значением, если их не разыменовывать — оказалось, что std::size не может работать со ссылками[23]:
// Самодельный std::size для массивов, в STL G++ похожий код
template <typename T, size_t N>
constexpr size_t array_size(T (&)[N]) { return N; }
void check(int const (¶m)[3]) {
int local[] = {1, 2, 3};
constexpr auto s0 = array_size(local); // OK
constexpr auto s1 = array_size(param); // Теперь OK
}
Разрешены Шаблон:Cpp-переменные в Шаблон:Cpp-функциях[24]:
constexpr char xdigit(int n) {
static constexpr char digits[] = "0123456789abcdef";
return digits[n];
}
Это позволит, например, написать Шаблон:Cpp[25].
Лямбда-функции и Шаблон:Cpp-шаблоны могут вызывать невычисляемый Шаблон:Cpp и тогда сами становятся Шаблон:Cpp[26]. Для первой никак нельзя указать, что она Шаблон:Cpp. А шаблон теперь может стать условным Шаблон:Cpp в зависимости от пути инстанцирования.
Статические Шаблон:Cpp и Шаблон:Cpp
Убирает одну машинную команду, если класс без данных и компилятор почему-то отказался от инлайнинга (добавления в вызывающий код вместо создания отдельной функции)[27]. Например, в самобалансирующемся дереве с нестандартным порядком (было в Си++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[28].
Эти операции всё ещё нельзя определять вне класса.
Аннотация [[assume(bool)]]
Разрешено аннотировать только пустой оператор. Код в аннотации никогда не исполняется, даже если имеет побочные эффекты. Служит исключительно для оптимизатора — он может закладываться на данное выражение. Если выражение будет равняться Шаблон:Cpp, то это неопределённое поведение. Функция Шаблон: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 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) и расчёты с неопределённым поведением[29].
Новые правила синтезированной Шаблон:Cpp и перевёрнутых Шаблон:Cpp/Шаблон:Cpp
Чтобы писать меньше нетворческого кода, в С++20 сделали синтез операции «не равняется» из «равняется», и примерку обеих как в обычном виде, так и в перевёрнутом. Это сильно ударило по имевшемуся коду: на G++ работает с предупреждением, например, такой саморекурсивный шаблон[30] — выбирает между простой и перевёрнутой операцией Шаблон:Cpp:
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): как вызвать ошибку, если ни одна ветвь Шаблон:Nobr? Шаблон:Cpp Шаблон:Nobr — компилятор выдавал ошибку, даже Шаблон:Nobr; приходилось запутывать компилятор так, чтобы он не видел тождественный Шаблон:Cpp. Общепринятое решение — шаблон Шаблон:Cpp, который пишут прямо на месте[31].
В С++23 Шаблон:Cpp в шаблонах проверяется при инстанцировании, даже если выражение можно вычислить, Шаблон:Nobr. Если он должен проверяться всегда — вытащите его за пределы шаблона.
// Больше не нужно
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, разобрать монолитные символы на компоненты и собрать снова). Если нет — программа некорректна.
Это изменение только делает поддержку Юникода более целостной, но никак не решает вопросов атак через внешне одинаковые строки[32]. Методы передачи таких символов в линкер остаются за реализацией.
Запрещены многосимвольные и некодируемые wchar_t-литералы
Разные компиляторы действовали по-разному на Шаблон:Cpp (эмодзи «фейспалм») на двухбайтовом wchar_t (Windows), Шаблон:Cpp. Теперь оба запрещены[33].
Многосимвольные char-литералы продолжают работать, имеют тип int. Сколько допускается символов и как они будут собраны в одно число — определяется реализацией.
Понятия «кодировка файла», «кодировка трансляции», «кодировка исполнения»
Узаконено, что они могут отличаться[34], и Шаблон:Cpp — это единица широкой, зависящей от реализации кодировки исполнения[35]. Кодировка трансляции неизменна и покрывает весь Юникод, включая не существующие пока символы и несимвольные позиции, за исключением, возможно, половинок суррогатных пар. Кодировки файлов тоже бывают разные.
UTF-8 как кроссплатформенная кодировка файлов должна поддерживаться безусловно, всеми компиляторами[36]. Должен существовать флаг компилятора, объявляющий все файлы UTF-8 независимо от наличия в них метки порядка байтов Юникода. Метка не должна мешать компиляции; мало того, её наличие — допустимая тактика опознания юникодных файлов. В файле Юникода не должно быть некорректных кодовых комбинаций, однако могут быть корректные комбинации, соответствующие не существующим пока символам. Прочие принципы перекодировки задаются реализацией.
Числовые значения символьных литералов в препроцессоре совпадают с кодировкой исполнения
Раньше это было за реализацией, но оказалось, что основное назначение этой функции — определение кодировки исполнения[37]. Например, код из SQLite:
/* Проверка, использует ли машина EBCDIC.
(Да, верите или нет, ещё есть машины, использующие EBCDIC.) */
#if 'A' == '\301'
# define SQLITE_EBCDIC 1
#else
# define SQLITE_ASCII 1
#endif
Все крупные компиляторы фактически работают именно так.
Снова разрешено инициализировать массивы (unsigned) char литералом UTF-8
Все три строчки сломаны в Си++20, снова работают в Си++23[38].
const char* a = u8"a";
const char b[] = u8"b";
const unsigned char c[] = u8"c";
Как оказалось, подобный слом требовал constexpr-преобразование Шаблон:Cpp↔Шаблон:Cpp (Шаблон:Cpp — не constexpr), мешал брать код на Си++14/17 и развёртывать на 20 (нередко нужны упомянутые преобразования), нарушал совместимость с Си.
Новые экранировки
Шаблон:Cpp для кодовой позиции Юникода, Шаблон:Cpp для восьмеричной системы и Шаблон:Cpp для шестнадцатеричной[39].
Разрывать такие экранировки (Шаблон:Cpp) запрещено.
Шаблон:Cpp позволяет обратиться к символу по юникодному имени[40].
format перекодирует между Юникодом и однобайтовой кодировкой
Исключает крокозябры в таком коде[41]:
std::locale::global(std::locale("Russian.1251"));
auto s = std::format("День недели: {}", std::chrono::Monday);
Набор возможных кодировок определяется реализацией. При неспособности — выбрасывается исключение.
Уточнены принципы расчёта ширины в консоли
Для каждого текста реализация сама определяет, сколько позиций терминала он займёт. Описана эталонная юникодная реализация: большинство восточноазиатских символов и эмодзи имеют ширину 2, все умляуты — 0, у остальных 1. Реализация локаленезависима. Ширина символа-заполнителя всегда принимается за 1[42].
Специальные числа вроде NaN и ∞ не заполняются нулями.
// Ширина эмодзи — 2
string sB = format("{:🤡^6}", "x"); // 🤡🤡x🤡🤡🤡
string sC = format("{:*^6}", "🤡🤡🤡"); // 🤡🤡🤡
double inf = numeric_limits<double>::infinity();
string s4 = format("{:06}", inf); // ␣␣␣inf
Редакционные правки
- При объединении строк через обратную косую черту теперь допустимы пробелы после этой черты[43]. Так действовали GCC, Clang и ICC — а MSVC считал это заэкранированным пробелом.
- Компилятор лишён права на перестановку полей одного объекта, если те имеют ненулевую длину и разные права доступа[44]. Так действовали MSVC, GCC, Clang.
- Запрещена конкатенация строк с противоречивыми префиксами кодировки вроде Шаблон:Cpp. Из крупных компиляторов такое поддерживает только SDCC — берёт первый из префиксов[45].
- Узаконена директива Шаблон:Cpp, поддерживаемая всеми[46].
- Упрощены правила неявного перемещения при возврате из функции[47].
- Переработаны производителезависимые расширенные целые типы[48].
- Шаблон:Cpp и Шаблон:Cpp теперь TriviallyCopyable[49].
- В модулях запрещено экспортировать бессмысленные вещи вроде Шаблон:Cpp и Шаблон:Cpp[50]. Однако составные конструкции, состоящие из Шаблон:Cpp, для простоты экспортировать можно: Шаблон:Cpp. (Например, в определённых настройках препроцессора в скобках остался один Шаблон:Cpp.)
- Компилятор вправе игнорировать стандартные атрибуты[51]. Единственный из них, который влияет на работу программы,— Шаблон:Cpp, и его игнорировать можно было и раньше. Остальные влияют на предупреждения или дают дополнительную информацию компилятору. А то, что влияет на программу, например Шаблон:Cpp,— не атрибут.
Уточнено, какие временные объекты сохраняются до конца цикла
В конструкции «for по объекту» говорится: ради безопасности и предсказуемости кода каждый временный объект, в нормальных условиях исчезающий в конце строки, сохраняется до конца цикла[52]. Исключением являются переданные по значению параметры функции — ведь они, исчезая как объекты в конце строки, вероятно, теряют своё содержимое раньше.
Это четвёртый случай, когда нарушено правило «деструкторы вызываются сразу же после окончания выражения». Остальные три: создание и копирование массива (ради простоты и эффективности для каждого элемента сначала исчезают временные, потом создаётся/копируется следующий), явная команда придержать объект через Шаблон:Cpp.
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[53].
- Разрешена метка без оператора: Шаблон:Cpp[54].
- Поддержка Шаблон:Cpp. Аналога Шаблон:Cpp нет[55].
- Снова разрешено инициализировать массивы char и unsigned char литералом UTF-8 (описано выше).
- Уточнён статус заголовочных файлов Си: изначально они были запрещённые, теперь — для совместимости. Файл, который не должен быть одновременно допустимым файлом Си, не должен их подключать[56]. Это убирает угрозу: в ближайшее время эти заголовки не удалят.
- Добавлен новый бит метода открытия файла на запись Шаблон:Cpp: файла не должно существовать. Это исключает перезапись нужного файла, исключает гонки за файл между двумя программами. Бит существовал во многих реализациях до Cи++98, и эквивалентен флагу Шаблон:Cpp Си11 (Шаблон:Cpp) и отсутствию флага CREATE_ALWAYS в Win32. Например, может служить для поиска неиспользуемого временного файла[57].
Шаблон:Cpp, Шаблон:Cpp — адаптеры между умными указателями и Си-API
Эти обёртки призваны «подружить» «сырые» Си-API и умные указатели[58][59]. Исчезая, обёртка перезаписывает указатель. В любом случае в типе умного указателя программист должен верно указать, какой функцией объект уничтожать.
// Межъязыковой 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[60].
- Функция Шаблон:Cpp для преобразования Шаблон:Cpp, более понятная по названию и менее ошибкоопасная[61].
- Функция Шаблон:Cpp в заголовке Шаблон:Cpp для смены порядка байтов в числах[62].
- iostream теперь может печатать volatile-указатели — точно так же, как и обычные[63].
- Шаблон:Cpp — аналог Шаблон:Cpp для объекта и его поля; понадобился из-за this-параметров. Означает «передать наподобие», и делает прямую передачу поля Шаблон:Cpp[64].
- Зарезервированы два модуля: Шаблон:Cpp (всё пространство имён Шаблон:Cpp) и Шаблон:Cpp (функции совместимости вроде Шаблон:Cpp из стандартного пространства имён)[65]. На ноябрь 2022 модулей не поддерживает никто, но, по заявлениям «Открытых систем», даже компиляция Hello World серьёзно ускорилась[66].
- Переписаны концепции Шаблон:Cpp и другие, чтобы поддерживали некопируемые типы[67].
- Больше совместимости между кортежами (tuple) и кортежеподобными объектами (парами, статическими массивами)[68].
- Шаблон:Cpp — нехранящий многомерный массив[69]
- Усовершенствован Шаблон:Cpp для работы с типами, унаследованными от Шаблон:Cpp (обычно какие-то реализации конечных автоматов)[70].
- Уточнено, что Шаблон:Cpp — всегда ссылка, а не Шаблон:Cpp[71].
- У библиотеки изначально были два варианта: автономная (freestanding) и платформенная (hosted); автономная не содержит системных вызовов, может писаться даже на чистом Си++, и будет работать даже на нестандартных машинах без консоли и файловой системы. В автономную версию Шаблон:Cpp внесён полностью, а Шаблон:Cpp и Шаблон:Cpp — частично[72].
- Шаблон:Cpp, используемое при сложной композиции шаблонов[73].
- Ослаблены требования к функции Шаблон:Cpp, чтобы можно было налаживать разные виды часов, в том числе хранящие некое состояние (например, синхронизирующиеся по интернету через NTP)[74].
Шаблон:Cpp — информация о вложенности вызовов
Одно из важнейших нововведений Си++23, позволяющее видеть ошибку вернее, чем короткие сообщения самопроверок[75]. Например: если случился выход за пределы массива, самопроверка скажет: обращался к 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 придумали стандартную функцию форматирования[76].
Как связывать stacktrace с выпадающими авариями — пока не придумали, ведь то и другое — довольно тяжёлые части языка и библиотеки.
Шаблон:Cpp — облегчённая некопируемая обёртка для объектов-функций
Функциональное программирование в Си++ обычно (например, в STL) сделано через шаблоны. Но шаблоны не всегда годятся: внешняя функция (например, Шаблон:Cpp) бывает большая, или в шаблонном виде выставляет наружу много лишнего, или принципиально не шаблонная (виртуальная). Да и нешаблонную функцию проще отлаживать. В Си++11 это частично решено обёрткой Шаблон:Cpp.
Шаблон:Cpp стал одной из самых «тяжёлых» частей библиотеки STL. Избавившись от нескольких возможностей — нельзя копировать, отсутствуют поля Шаблон:Cpp и Шаблон:Cpp — можно получить значительно более лёгкий[77] объект[78]. Этот объект может работать с некопируемыми перехватами.
Аппаратно-разнородные барьеры
Объект блокирующей межпоточной синхронизации «барьер» используется для частично распараллеливаемых задач: как только каждый поток выполнит свою долю и все соберутся у барьера, выполняется координационная функция и решает, что делать дальше. В реализации для Си++ поток-координатор и ожидающие очередного этапа вычислений потоки-клиенты должны вызвать Шаблон:Cpp, а потоки-работники — Шаблон:Cpp при обычном исполнении и Шаблон:Cpp, если поток выходит из параллельного вычисления.
Координационная функция теперь может выполниться в любом потоке: не только в последнем Шаблон:Cpp, но и в Шаблон:Cpp[79]. Что будет, если никто не вызовет Шаблон:Cpp (то есть выделенного координатора нет),— зависит от реализации. Связано с разнородным аппаратным обеспечением — координатор и клиенты работают на одной архитектуре, а потоки-работники на другой.
Шаблон:Cpp — результат или код ошибки
У обработки ошибок есть четыре важных свойства:
- Заметность: ревизору должны быть видны нарушения методики использования функции.
- Информация об ошибках: ошибка должна нести достаточно информации о том, откуда она взялась и как её решить.
- Чистый код: код обработки ошибок должен быть минимален.
- Невмешательство: ошибки не должны забивать какой-нибудь канал, предназначенный для нормального хода исполнения.
Главный недостаток исключений — незаметность. У кодов ошибок как минимум грязный код, и они забивают важный канал — возвращаемое значение[80].
Шаблон: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
Монада — стандартная возможность функциональных языков произвести последовательность действий.
В математике последовательность функций записывается как , что не всегда удобно — в программировании часто лучше Шаблон:Cpp.
Шаблон:Cpp — довольно простая обёртка, смысл которой — хранить объект или ничего. Проверки на «ничего» занимают немалую часть работы с optional — а что, если в процессе преобразований картинки на ней не окажется кота? А что, если нет места, куда пририсовать бантик?[81]
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[82].
spanstream — замена запрещённому в C++98 strstream
Существовал Шаблон:Cpp — поток данных, работающий на массиве ограниченной длины. Из-за угрозы переполнений запрещён уже в 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); // гарантируется нуль-терминирование
Шаблон:Cpp — прямая (не потоковая) печать в консоль
Изначально было: Шаблон:Cpp
Это…
- Удлиняет двоичный код — потоки изначально тяжелы.
- Под Windows почти гарантированно будут крокозябры, даже если исходный текст в UTF-8.
- Выглядит некрасиво.
Доступен более лёгкий Шаблон:Cpp[83].
Впоследствии уточнили, что Шаблон:Cpp синхронизирован с другими методами вывода в консоль[84].
Необязательные дробные типы
| Название | Битов мантиссы/ порядка |
Шаблон:Comment | Шаблон:Comment | Примечание |
|---|---|---|---|---|
| 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 | Шаблон:Nobr | 1.9e-34 |
Соответствует IEEE binary128 |
У всех этих типов неявная единица: целая часть мантиссы 1,xxx только подразумевается и не хранится.
Математические функции должны иметь обёртки для всех поддерживаемых типов — при этом реальный расчёт может вестись в более или менее точном типе[85].
Новая функциональность диапазонов (ranges) и представлений (views)
- Из концепции Шаблон:Cpp удалена инициализация без параметров[86].
- Уточнены требования к дипазонам[87], представлениям[88], адаптерам диапазонов[89].
- Механизмы для написания собственных адаптеров диапазонов[90]
- Семейство адаптеров Шаблон:Cpp для параллельного прохождения разных диапазонов[91].
- Шаблон:Cpp — преобразование из диапазона в контейнер[92].
- Функции Шаблон:Cpp, Шаблон:Cpp, Шаблон:Cpp[93].
- Функции Шаблон:Cpp и Шаблон:Cpp[94].
- Функция Шаблон:Cpp[95].
- Функция Шаблон:Cpp[96].
- Улучшена работа Шаблон:Cpp с итераторами, которые возвращают ссылку на что-то внутри самого итератора[97].
- Функции Шаблон:Cpp, Шаблон:Cpp[98].
- Шаблон:Cpp переименован в Шаблон:Cpp, добавлен новый Шаблон:Cpp[99].
- Ослаблены ограничения на Шаблон:Cpp[100].
- Шаблон:Cpp можно строить из непрерывного диапазона[101]. Впоследствии уточнили: конструктор явный (explicit)[102].
- Механизмы вывода диапазонов функцией Шаблон:Cpp[103].
- Добавлены механизмы указания, как печатать нестандартный диапазон функцией Шаблон:Cpp — вывод запрещён, как отображение (map), как множество (set), как кортеж (sequence), как строку (string), как отладочное сообщение (debug_string).[104].
- Шаблон:Cpp — синхронный генератор диапазонов на сопрограммах[105]. Налажено форматирование подобных объектов[106]. Первое использование сопрограмм в стандартной библиотеке.
- Шаблон:Cpp и другие адаптеры-представления теперь могут работать с перемещаемыми, но не копируемыми типами[107].
- Новая функция-адаптер Шаблон:Cpp, повторяющая один объект N раз или до бесконечности[108].
- Переписаны требования к алгоритмам, чтобы они могли пользоваться итераторами диапазонов[109].
- Функция Шаблон:Cpp — декартово произведение диапазонов[110][111].
- Функция Шаблон:Cpp, представляющая собой f(f(…f(f(init, x1), x2), …), xn)[112].
- Функции Шаблон:Cpp, Шаблон:Cpp[113].
- Объект Шаблон:Cpp, функция Шаблон:Cpp — представление с шагом N[114]
- Функции Шаблон:Cpp, Шаблон:Cpp, Шаблон:Cpp[115].
- Функция Шаблон:Cpp[116] — часто функцией Си++ «проход по контейнеру» не пользовались просто потому, что вдобавок требовался номер в последовательности.
Новые подсказки по выведению параметров шаблона (deduction guides)
- Шаблон:Cpp — теперь будет компилироваться Шаблон:Cpp, если less — шаблон с новой статической операцией «вызов»[27].
- Шаблон:Cpp и Шаблон:Cpp — теперь будет компилироваться Шаблон:Cpp, если F — объект, чья операция «вызов» содержит новый (также ожидаемый в Си++23) this-параметр[117]
Новая функциональность строк владеющих (string) и невладеющих (string_view)
- Шаблон:Cpp — часто надо проверить на наличие подстроки, не выясняя, где совпадение[118].
- Шаблон:Cpp (с временным Шаблон:Cpp) — для оптимизации Шаблон:Cpp[119].
- 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?[18] Дело в том, что стоимость выделения памяти мало зависит от длины буфера, и в большинстве случаев на буфер будет выделено значительно больше памяти, чем реально потребуется на сжатую строку. Новая функция не инициализирует буфер, и ушло зануление очень длинного участка памяти — Шаблон:Cpp.
Новая функциональность итераторов
- Адаптер Шаблон:Cpp — теперь итератор того же вида (односторонний/двусторонний/произвольного доступа), что и исходный итератор (раньше только односторонний)[120].
- Переписаны требования к алгоритмам, чтобы они могли пользоваться итераторами диапазонов (см. выше).
- Шаблон:Cpp всегда должен возвращать константный итератор[121].
- Исправлена Шаблон:Cpp в Шаблон:Cpp и некоторых других, чтобы из них можно было собирать сложные диапазоны[122].
- Придумана концепция позаимствованных диапазонов — их итераторами можно продолжать пользоваться, когда объект-диапазон исчезает[123].
Новая функциональность контейнеров
- Конструкторы Шаблон:Cpp, принимающие пару итераторов[124].
- Шаблонному конструктору Шаблон:Cpp даны типы по умолчанию[125].
- Разнородный Шаблон:Cpp и Шаблон:Cpp в ассоциативных контейнерах[126]. Например, ключ хранения Шаблон:Cpp, а ключ доступа — Шаблон:Cpp.
- Переписыванием конструкторов сделано, чтобы аллокатор не участвовал в разрешении перегрузок[127].
- Новая функция Шаблон:Cpp, более полно использующая особенности механизма выделения памяти[128]. Контейнеры переменного размера понемногу будут переходить на неё.
- Новые контейнеры Шаблон:Cpp и Шаблон:Cpp, работающие как минимум на шаблонах Шаблон:Cpp, Шаблон:Cpp, Шаблон:Cpp. Представляют собой простые сортированные массивы.
Новые constexpr
- Больше Шаблон:Cpp в Шаблон:Cpp[129].
- Целочисленные Шаблон:Cpp, Шаблон:Cpp[130].
- Почти все функции Шаблон:Cpp[131] — после того, как Шаблон:Cpp и Шаблон:Cpp стали выполняться в этом контексте.
- Математика из Шаблон:Cpp, Шаблон:Cpp[132].
- Большинство функций Шаблон:Cpp, Шаблон:Cpp[133].
- Шаблон:Cpp[134]
Новая функциональность format
Помимо стандартного форматирования новых объектов (описано в соответствующих разделах), там есть:
- Форматирование Шаблон:Cpp[76].
- Проверка корректности форматирования при компиляции, если такое возможно; облегчение Шаблон:Cpp, если обработка при компиляции удастся[135].
- Открыт Шаблон:Cpp. Автоматическая проверка корректности Шаблон:Cpp при компиляции ограничивается функцией Шаблон:Cpp, и пока обходной способ добавить проверку в свою функцию (например, Шаблон:Cpp — писать собственные реализации Шаблон:Cpp, пользуясь по максимуму штатными библиотеками[136]. Для последнего и нужен Шаблон:Cpp. Предполагается, что в новых версиях проверка будет сделана чище — например, через проверку параметров при компиляции.
Оптимизации и предупреждения
- Шаблон:Cpp получил условный noexcept — если объект создаётся с перемещением и присваивается (с перемещением или по копии, в зависимости от правого параметра), не вызывая исключений[137].
- Шаблон:Cpp получил такой же условный noexcept[138].
- Некоторые библиотечные функции наподобие Шаблон:Cpp и Шаблон:Cpp ещё с Си++20 получили прозвище «благословенные» — они могут неявно создавать объекты. То есть: не оперируя типом X, тем не менее, подразумевают, что в памяти может появиться объект типа X. Теперь можно «благословить» любую функцию кодом Шаблон:Cpp — и подобные вызовы не будут неопределённым поведением[139].
- Типы, с которыми такие «благословенные» функции могут работать, названы «типы с неявным временем жизни» — для них придумана функция Шаблон:Cpp[140].
- Новые свойства типов Шаблон:Cpp, Шаблон:Cpp. Некоторые объекты (Шаблон:Cpp) теперь не могут конструироваться из подобных объектов[141].
- Добавлен псевдоконструктор Шаблон:Cpp — как подсказка: строить строку из пустого указателя запрещено.
Функция unreachable
Функция, указывающая, что при нормальной работе кода в данную точку попасть нельзя[142]. Поведение неопределённое, вызов этой функции в действующем коде — всегда нештатная ситуация.
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 много лет существовала под названием Шаблон:Cpp. Но даже без «волшебства» подходящая реализация простейшая: Шаблон:Cpp с пустым телом.
Первоапрельские предложения
Предложение 1 апреля 2021 отсылало на My Little Pony, введя два ключевых слова «my» и «little», и объект «pony» — волшебный объект, делающий что угодно[143]. Кроме того, высмеивало программистский жаргон:
- Пони-ориентированное программирование, а также «Пони-объектно-ориентированное программирование» (POOP — «какашка») — всё новые и новые концепции программирования.
- В жаргоне групп Си++ появилось понятие «tony table» (таблица «было-стало») — непонятно, откуда, ведь автором первой из них был отнюдь не Тони. Предложение переименовало её в «pony stable».
- Последний раздел называется «Формулировка» — стандарт Си++ пишется таким канцеляритом, что там одно предложение: «Не говорите, что дочитали до этого места».
Оставлены на будущее
- Шаблон:Cpp — загрузка массива из двоичного файла.
- Ситуация с Юникодом в диагностических строках (Шаблон:Cpp и других).
- Атрибуты для структурного связывания Шаблон:Cpp
- Шаблон:Cpp — более мощная версия Шаблон:Cpp.
- Стековые сопрограммы (в Си++20 только с бесстековые).
- Проверка параметров функции при компиляции: чтобы проверить, что форматная строка и параметры Шаблон:Cpp соответствуют друг другу, в Си++20 применяются сложные шаблоны. Поддержка со стороны языка упростила бы это.
Примечания
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2327r1.pdf
- ↑ Шаблон:Cite web
- ↑ P2360R0: Extend init-statement to allow alias-declaration
- ↑ CWG Issue 2397
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ 18,0 18,1 Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ consteval needs to propagate up
- ↑ 27,0 27,1 Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2460r2.pdf
- ↑ https://isocpp.org/files/papers/P2295R6.pdf
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ P2615R1: Meaningful exports
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ [1]Шаблон:Wayback P1642R11: Freestanding Library: Easy [utilities], [ranges], and [iterators]
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ 76,0 76,1 Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web