Любятово (Псков)
- 1 year ago
- 0
- 0
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). В микроконтроллерах часто используются многобитные порты, спроецированные в память
, потому разрешили побитовые операции
&=
,
|=
,
^=
(остальных не нашли в
открытом
коде). В последний момент разрешили и остальные
.
const
char
*
s
=
u8
"123"
;
(нарушено в C++20, см. ниже).
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
{}
.
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
;
};
Более раннее
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
{}
.
Простой способ получить объект как временный, например :
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
[]
и разрешить его за пределами класса.
Одна из возможностей Си++ — 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
означает, что есть хотя бы один путь исполнения, возможный при компиляции.
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 (¶m)[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 и программист не написал операцию != сам. В некоторых случаях компилятор может запутаться и сказать: есть выбор между обычной и перевёрнутой операцией «равняется», и исправление этой ошибки простое — поступить по старинке и написать операцию «не равняется» самостоятельно.
У нового перечисления вариантов через
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 (остальные).
Идентификатор должен быть нормализован по алгоритму «каноническая композиция» (NFC, разобрать монолитные символы на компоненты и собрать снова). Если нет — программа некорректна.
Это изменение только делает поддержку Юникода более целостной, но никак не решает вопросов атак через внешне одинаковые строки . Методы передачи таких символов в линкер остаются за реализацией.
Разные компиляторы действовали по-разному на
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
Все крупные компиляторы фактически работают именно так.
Все три строчки сломаны в Си++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}"
позволяет обратиться к символу по юникодному имени
.
Исключает в таком коде :
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
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
>
нет
.
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
>
для смены
порядка байтов
в числах
.
forward_like
— аналог
forward
для объекта и его поля; понадобился из-за this-параметров
.
import
std
;
(всё пространство имён
std
::
) и
import
std
.
compat
;
(функции совместимости вроде
::
fopen
из стандартного пространства имён)
. На ноябрь 2022 модулей не поддерживает никто, но, по заявлениям «
Открытых систем
», даже компиляция
Hello World
серьёзно ускорилась
.
equality_comparable_with
и другие, чтобы поддерживали некопируемые типы
.
mdspan
— нехранящий многомерный массив
visit
для работы с типами, унаследованными от
variant
(обычно какие-то реализации
конечных автоматов
)
.
common_reference_t
— всегда ссылка, а не
reference_wrapper
.
<
utility
>
внесён полностью, а
<
ranges
>
и
<
iterator
>
— частично
.
invoke_r
, используемое при сложной композиции шаблонов
.
time_point
::
clock
, чтобы можно было налаживать разные виды часов, в том числе хранящие некое состояние (например, синхронизирующиеся по интернету через
NTP
)
.
Одно из важнейших нововведений Си++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 с выпадающими авариями — пока не придумали, ведь то и другое — довольно тяжёлые части языка и библиотеки.
std
::
function
стал одной из самых «тяжёлых» частей библиотеки STL. Избавившись от нескольких возможностей — нельзя копировать, отсутствуют поля
target
и
target_type
— можно получить значительно более лёгкий
объект
. И, разумеется, этот объект может работать с некопируемыми перехватами.
В объекте синхронизации «
барьер
» предполагается, что поток-координатор и ожидающие барьера потоки-клиенты должны вызвать
wait
, а потоки-работники —
arrive
при обычном исполнении и
arrive_and_drop
, если поток выходит из параллельного вычисления.
Координационная функция теперь может выполниться в любом потоке: не только в последнем
arrive
, но и в
wait
. Что будет, если никто не вызовет
wait
(то есть координатора нет),—
зависит от реализации
. Связано с разнородным аппаратным обеспечением — координатор работает на одной архитектуре, а потоки-работники на другой.
У обработки ошибок есть четыре важных свойства:
Главный недостаток исключений — незаметность. У кодов ошибок как минимум грязный код, и они забивают важный канал — возвращаемое значение .
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 — а что, если в процессе преобразований картинки на ней не окажется кота? А что, если нет места, куда пририсовать бантик?
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
.
Существовал
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); // гарантируется нуль-терминирование
Изначально было:
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 только подразумевается и не хранится.
Математические функции должны иметь обёртки для всех поддерживаемых типов — при этом реальный расчёт может вестись в более или менее точном типе .
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
— часто функцией Си++ «проход по контейнеру» не пользовались просто потому, что вдобавок требовался номер в последовательности.
function
— теперь будет компилироваться
std
::
function
f
=
less
<
int
>
{};
, если less — шаблон с новой статической операцией «вызов»
.
function
и
packaged_task
— теперь будет компилироваться
std
::
function
g
=
F
{};
, если F — объект, чья операция «вызов» содержит новый (также ожидаемый в Си++23) this-параметр
string
[
_view
].
contains
— часто надо проверить на наличие подстроки, не выясняя, где совпадение
.
string
.
substr
()
&&
(с временным
this
) — для оптимизации
auto
a
=
someTemporaryString
().
substr
(
0
,
2
);
.
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
?
Дело в том, что стоимость выделения памяти мало зависит от длины буфера, и в большинстве случаев на буфер будет выделено значительно больше памяти, чем реально потребуется на сжатую строку. Новая функция не инициализирует буфер, и ушло зануление очень длинного участка памяти —
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
в
bitset
.
from_chars
,
to_chars
.
unique_ptr
— после того, как
new
и
delete
стали выполняться в этом контексте.
cmath
,
cstdlib
.
optional
,
variant
.
type_info
::
op
==
Помимо стандартного форматирования новых объектов (описано в соответствующих разделах), там есть:
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
— как подсказка: строить строку из пустого указателя запрещено.
Функция, указывающая, что при нормальной работе кода в данную точку попасть нельзя .
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
.
format
соответствуют друг другу, в Си++20 применяются сложные шаблоны. Поддержка со стороны языка упростила бы это.