Народная Республика Болгария
- 1 year ago
- 0
- 0
C++26 или C++2c (латиницей), или Си++26 (кириллицей) — ожидаемый стандарт языка программирования C++ . Разработка началась сразу же после того, как в феврале 2023 года зафиксировали Си++23 .
Си++0x должен был приблизить Си++ к современным языкам, непрерывно разрабатываемым под руководством единоличника . (Си++ разрабатывается комитетом и есть много реализаций — в отличие от, например, Python .) Но стандарт запоздал, и с версии 14 новый язык выпускают не «когда готово», а раз в три года, при этом последний год — только доводка. Потому ещё до окончания работы над версией 23 стандарт называли «Си++26».
enum
и дробным;
enum
и другим enum. Ошибкоопасное наследие Си. Запрещены в Си++20, операция «звездолёт»
<=>
никогда не разрешалась
. Использовать явное преобразование типов. Может бросить тень на совместимость с Си, обходится легко:
+
C1
+
C2
.
<
codecvt
>
— нет обработки ошибок
. При преобразовании кодировок, даже разных форматов
сериализации
Юникода
вроде
UTF-8
→
16
, в зависимости от того, откуда источник взялся, какой вред может нанести и что лучше для пользователя, возможно: объявить
общую аварию
программы, сообщить о плохих данных, опустить неудачные последовательности, поставить на их месте U+FFFD �
replacement character
или что-то ещё. Запрещён в Си++17. Использовать внешние, более управляемые функции.
allocator
<
T
>::
is_always_equal
. Ошибкоопасен при наследовании от аллокатора, в котором этот is_always_equal есть. Запрещён в Си++20, для проверки возможностей аллокатора использовать
allocator_traits
. Использовать в собственных аллокаторах, когда это действительно играет роль.
string
.
reserve
()
без параметров, эквивалентный
reserve
(
0
)
. Со старым API строк (Си++98…17) использовалось как
shrink_to_fit
, им же и заменено. В Си++20
reserve
больше не укорачивает строку, а данную перегрузку запретили.
void
f
(
T
&&
...
t
)
{
g
(
std
::
forward
<
T
...[
0
]
>
(
t
...[
0
]));
}
. Формально это несколько бьёт по имеющемуся коду:
void
f
(
T
...[
0
]){}
представляло собой параметры без имён — пакет массивов, но по факту даже не компилировалось в MSVC и G++. C# и D поддерживают и i-й параметр с конца, но отрицательные числа для этого ошибкоопасны, а более сложный синтаксис решено не просить. Главная причина — ускорение компиляции: пакетные шаблоны часто исполняются за
O
(
n
²) и с глубокой вложенностью.
static_assert
Для начала придумали понятие «невычисляемая строка» (
unevaluated string
): закавыченная строка, значение которой не проходит в скомпилированную программу, а нужно только компилятору. Они являются частью
_Pragma
,
asm
,
[[
nodiscard
]]
… — и, конечно,
static_assert
. Им запрещается иметь префикс кодировки.
Впоследствии позволили в
static_assert
любую константно вычисляемую строку
:
// Было
template <typename T, auto Expected, unsigned long Size = sizeof(T)>
constexpr bool ensure_size() {
static_assert(sizeof(T) == Expected, "Unexpected sizeof");
return true;
}
static_assert(ensure_size<S, 1>());
// Остаётся надеяться, что компилятор напишет, что дело было в ensure_size<int, 1, 4>
// Стало
static_assert(sizeof(S) == 1,
std::format("Unexpected sizeof: expected 1, got {}", sizeof(S));
// Unexpected sizeof: expected 1, got 4
constexpr
format
намеренно не внесён, но его прообраз, библиотека libfmt, уже способна на constexpr.
_
может повторяться
auto
[
x
,
y
,
_
]
=
f
();
— давно устоявшаяся манера программирования, когда функция возвращает три поля, а нужны два, особенно если возвращается неговорящий тип вроде
tuple
. Второй вариант — когда
временный объект
не годится, а имя не важно: например, захват
мьютекса
lock_guard
_
(
someMutex
)
. На случай, когда таких
подчерков
несколько, идиому расширили:
namespace a {
auto _ = f();
auto _ = f(); // Остаётся ошибка: с глобальными переменными не работает
}
int _;
void f() {
using ::_; // Остаётся OK, добавление в пространство имён постороннего символа
auto _ = 42; // Теперь OK
using ::_; // Остаётся ошибка: using _ разрешено только до локальной _
auto _ = 0; // Теперь OK
static int _; // Остаётся ошибка: со статическими переменными не работает
{
auto _ = 1; // Остаётся OK, замещение
assert( _ == 1 ); // Остаётся OK, имеем дело с замещённой переменной
}
assert( _ == 42 ); // Ошибка: которая из двух?
}
enum MmapBits {
Shared,
Private,
_,
_, // OK: пропустить два
Fixed,
Rename,
};
Для функций, типов,
using
и
концепций
новый механизм бесполезен: этим объектам либо нужно говорящее имя, либо Си++ уже даёт подходящие механизмы вроде
безымянных типов
.
void
*
, а потом обратно в свой тип
. Преобразование в посторонние типы неконстантно. Используется для так называемого
стирания типа
— при выполнении информация хранится в переменной общего типа, но её обработка выстраивается так, что все преобразования в исходный тип верны. (Так устроены, например,
обобщённые типы
Java
.) В
CLang
механизм уже есть (потребовался для выделения памяти) и вынести наружу ничего не стоит,
G++
и
не видят препятствий. По заявлениям
Г. Саттера
, это шаг к
constexpr
format
.
\⤶
и склеиванием лексем через препроцессорное
##
можно получить имя символа; переводы строк внутри закавыченной строки запрещены. Это
статус-кво
, поддерживаемый G++, CLang и
.
__has_cpp_attribute
должна проверять,
реагирует ли
компилятор на данный атрибут (а не разбирает ли) — а если разбирает, но не реагирует, атрибут бесполезен и макросы совместимости должны развёртываться в компиляторозависимые функции вроде
__builtin_assume
. (А это новое правило.)
initializer_list
по возможности хранятся в
, желательно неизменяемом
.
generate_canonical
переписаны так, чтобы сохранялись статистические свойства на всём диапазоне [0,1) — и результирующее число никогда из-за недостатков дробной арифметики не стало бы единицей
.
@$`
, которые когда-нибудь на что-нибудь пригодятся
. Ранее в Си23 добавили
@$
, в первую очередь из-за
EBCDIC
, где в разных вариантах «собака» в разных местах и надо её идентифицировать и корректно перекодировать
.
strtok
из автономной библиотеки вслед за Си
— содержит внутреннее состояние. Большинство реализаций используют потоколокальные переменные, которые в автономной среде могут отсутствовать.
assert
, чтобы лучше поддерживались шаблоны и многомерная индексация, коих просто не существовало на момент появления
препроцессора Си
.
fstream
. Может использоваться в высоконадёжном программировании, когда надо гарантированно записать данные на диск
.
<
debugging
>
с тремя функциями:
breakpoint
()
,
breakpoint_if_debugging
()
,
bool
is_debugger_present
()
.
Автономная ( freestanding ) библиотека не полагается на системные вызовы (даже выделение памяти), может быть написана даже на чистом Си++ и потому полностью кроссплатформенна.
operator
new
, возвращающий
nullptr
, приводящий к системной аварии или делающий что угодно по желанию реализатора. Добавлен макрос
__cpp_lib_has_default_operator_new
, проверяющий, возможно ли выделение памяти — например, вместо динамического
std::vector
могут использоваться массивы ограниченного размера
.
<
charconv
>
и
char_traits
.
algorithm
,
array
,
optional
,
variant
,
string_view
. Переписаны монадные функции
optional
так, чтобы не ссылались на неавтономный (выбрасывающий исключения)
value
.
expected
,
span
,
mdspan
.
strtok
вслед за Си
— качественная реализация использует потоколокальные переменные, которые в автономной среде могут отсутствовать.
consteval
bool
is_within_lifetime
(
&
union_
.
field
)
— «волшебная» (реализованная внутри компилятора) функция, проверяющая, держит ли union то или иное поле
. Ведь при компиляции
union
становится помеченным на манер
variant
, а доступ к другому полю отключает расчёт при компиляции. Используется для экстремальной оптимизации по памяти с сохранением константности — например, для однобайтового
optional
<
bool
>
.
from_chars_result
получил
operator
bool
— проверку кода ошибки.
to_string
для дробных выдаёт то же, что и
format
(
"{}"
,
x
)
— в компактном нелокализованном виде. Ранее он был унифицирован с
printf
(
"%f"
,
x
)
, то есть обращался к глобальной локали (ненадёжно, да и вычисление нужных параметров локали затратно)
и плохо работал со слишком большими/малыми числами
.
stringstream
можно инициализировать строками
string_view
.
bitset
.
std
::
vformat
(
str
,
std
::
make_format_args
(
path
.
string
()));
→
std
::
format
(
std
::
runtime_format
(
str
),
path
.
string
());
. Первое предназначено для писателей своих обёрток над форматированием вроде
doLog
(
str
,
args
...)
, а не для конечных пользователей, и склонно к ошибкам: make_format_args содержит string_view, и если его вытащить в отдельную переменную, string_view будет жить дольше, чем временная строка. Для надёжности тонкая обёртка runtime_format_string принимается только по временной ссылке
.
make_format_args
избавились от std::forward и
временных объектов
, делая форматирование более устойчивым к
висячим ссылкам
.
copyable_function
, построенная по принципу новой
move_only_function
(Си++23) и значительно более лёгкая
, чем
function
(Си++11). Последнюю всё-таки решили не запрещать
.
function_ref
, не инкапсулирующая вызываемый объект, а просто ссылающаяся на него
.
bind_front
, если вызываемый объект (например,
слот
Qt
) вычисляется раз и навсегда при компиляции
.
visit
внутрь
variant
.
hash
для календарных типов
.
weak_ptr
.
owner_hash
и несколько других подобных функций
.
[
unordered_
]
set
/
map
: добавлены шаблонные
insert
,
insert_or_assign
,
try_emplace
,
operator
[]
,
bucket
. Разнородный поиск начат в Си++14, и позволяет хранить с «тяжёлыми» ключами (
string
), а искать по «лёгким» (
или даже
const
char
*
).
projected
(внутренний тип библиотеки диапазонов), лучше работающий с указателями на недоопределённые классы (
class
Opaque
;
). Многие из функций диапазонов не работали там, где работал «голый» STL
.
get
<
0
>
и
<
1
>
, как обычным кортежам (
tuples
)
.
basic_const_iterator
можно получить из неконстантного собрата
.
submdspan
, производящая
многомерных массивов. На выходе получается
mdspan
(Си++23), возможно, с нестандартным типом внутри
.
span
(
initalizer_list
)
, не требующий промежуточного объекта вроде массива
.
span
.
at
(
i
)
, выкидывающий
аварию
.
Объект хранится в динамической памяти. Как только этот объект изменили, создают новый такой же, а старый, когда можно, удаляют .
// Было — блокирующая версия
Data* data_;
std::shared_mutex m_;
template <typename Func>
auto reader_op(Func fn) {
std::shared_lock<std::shared_mutex> l(m_);
Data* p = data_;
return fn(p);
}
void update(Data* newdata) {
Data* olddata;
{ std::unique_lock<std::shared_mutex> wlock(m_);
olddata = std::exchange(data_, newdata);
}
delete olddata;
}
// Стало — не блокируются только читатели
std::atomic<Data*> data_;
template <typename Func>
auto reader_op(Func fn) {
std::scoped_lock l(std::rcu_default_domain());
Data* p = data_;
return fn(p);
}
void update(Data* newdata) {
Data* olddata = data_.exchange(newdata);
std::rcu_synchronize();
delete olddata;
}
Главный недостаток идиомы read-copy-update в данном исполнении — не ждут только читатели, писатель может надолго «зависать». Это «зависание» означает, что другие читатели работают и держат объект, но не всегда допустимо.
Hazard pointer дополнительно следит, какие потоки пользуются тем или иным объектом, и как только объект перестаёт использоваться, он исчезает .
Идиома похожа на
подсчёт ссылок
, но подсчитывает только локальные ссылки из функций доступа — а не глобальные ссылки между объектами. Это позволяет
циклические ссылки
без слежения, чей «ранг» выше (от «контейнеров» к «содержимому» —
shared_ptr
, в прочие стороны —
weak_ptr
), а также без присущего
shared
/
weak_ptr
управляющего объекта
, исчезающего, когда исчезнет последний
слабый указатель
.
Система сделана беспрепятственной по записи ценой повышенного расхода памяти: read-copy-update хранит одно поколение старых данных, а hazard pointer — сколько угодно .
Поскольку G++ всё ещё держит совместимость двоичных интерфейсов , на будущие дополнения оставили 4/8 байтов на объект.
// Стало — не блокируется и писатель
struct Data : std::hazard_pointer_obj_base<Data> {}
std::atomic<Data*> pdata_;
template <typename Func>
auto reader_op(Func userFn) {
std::hazard_pointer h = std::make_hazard_pointer();
Data* p = h.protect(pdata_);
return userFn(p);
}
void writer(Data* newdata) {
Data* old = pdata_.exchange(newdata);
old->retire();
}
Стандартная работа беззнаковых типов — арифметика остатков : при переходе через значение превращается в 0. Знаковые — зависят от реализации . Но это не всегда нужно: например, может означать «сколько угодно» и прибавление к нему единицы должно оставлять . Никакой защиты от дурака нет .
#include <numeric>
// Считаем, что у нас 8-битный char и отрицательные в дополнительном коде
int x1 = add_sat(3, 4); // 7
int x2 = sub_sat(INT_MIN, 1); // INT_MIN
unsigned char x3 = add_sat(255, 4); // 3!! — работа в int и преобразование 259 → 3
unsigned char x4 = add_sat<unsigned char>(255, 4); // 255
unsigned char x5 = add_sat(252, x3); // Ошибка, нет нужной перегрузки
unsigned char x6 = add_sat<unsigned char>(251, x2); // 251!! — преобразование INT_MIN → 0
Добавились BLAS -подобные алгоритмы линейной алгебры для заполненных (большей частью ненулевых) векторов и матриц . Мотивация:
Конструкция полностью шаблонная и на
mdspan
. Преимущества перед стандартным BLAS:
Пока вне рассмотрения: расширенные функции BLAS/ LAPACK , разреженная алгебра, расчёты повышенной точности, тензоры («матрицы» с тремя и более измерениями), параллельная работа, перегрузка операций ±. Последняя — из-за неоднозначности (есть несколько типов умножения векторов), данные могут быть в одном типе, а работа в другом, и из-за больших объёмов памяти и многоступенчатых расчётов промежуточные буфера часто используются повторно.
Нет даже решения заполненных СЛАУ . Вот одна из стандартных функций — решение треугольной СЛАУ на месте.
template<in-matrix InMat,
class Triangle,
class DiagonalStorage,
inout-vector InOutVec>
void triangular_matrix_vector_solve(
InMat A,
Triangle t,
DiagonalStorage d,
InOutVec b);
Здесь пустой тип-тэг Triangle показывает, каким треугольником собрана матрица, верхним или нижним. Аналогичный тэг DiagonalStorage — что представляет собой диагональ матрицы A: явные значения или неявные единицы. В векторе b изначально правая часть системы, в результате расчёта будет решение.
#embed
— инициализация массива данных двоичным файлом
, прошла в Си23.
inplace_vector
— массив изменяемой ограниченной длины на основе обычного массива
.
path_view
, аналог
string_view
для
путей
.
integral_constant
на более простой в использовании
constexpr_v
.
Крупные важные функции. Их давно просят, но нет гарантии, что будут готовы к сроку Си++26 .
inspect
, аналог
switch
, действующий даже на разные объектные подтипы и разные шаблоны строк