Пояснение причин и соответствующее обсуждение вы можете найти на странице Википедия:К удалению/8 августа 2023. Пока процесс обсуждения не завершён, статью можно попытаться улучшить, однако следует воздерживаться от переименований или немотивированного удаления содержания, подробнее см. руководство к дальнейшему действию. Не снимайте пометку о выставлении на удаление до подведения итога обсуждения.
Эта статья во многом или полностью опирается на первичные источники. Такие источники рассматриваются как нежелательные. А если они не опубликованы, то они не допустимы по правилу проверямости.
Вместо этого следует использовать опубликованные вторичные научные источники, рассматривающие данную тему. Вы можете отредактировать статью, добавив ссылки на вторичные научные источники.
Проверьте соответствие информации приведённым источникам и удалите или исправьте информацию, являющуюся оригинальным исследованием. В случае необходимости подтвердите информацию авторитетными источниками. В противном случае статья может быть выставлена на удаление.(12 сентября 2024)
C++26 или C++2c (латиницей), или Си++26 (кириллицей) — ожидаемый стандарт языка программирования C++. Разработка началась сразу же после того, как в феврале 2023 года зафиксировали C++23.
С самого начала стандарт получил рабочее имя «Си++26». Си++0x должен был приблизить устаревающий Си++ к современным языкам, непрерывно разрабатываемым под руководством единоличника. (Си++ разрабатывается комитетом и есть много реализаций — в отличие от, например, Python.) Но стандарт запоздал, и с версии 14 новый язык выпускают не «когда готово», а раз в три года, при этом последний год — только доводка. КОВИД не сместил расписание — к пандемии как раз была готова версия 20, а версию 23 подготовили дистанционно.
Ноябрь 2025, Каилуа-Кона (Гавайи, США) — ожидается
Запрещены и удалены
Запрещены в языке
voidf(intx...); — старый редкий синтаксис переменных параметров Си на разборе стека через va_list[6]. Остаётся добавленный позже Си-совместимый voidf(intx,...);. Возможно, это шаг к синтаксису «сколько угодно параметров int» шаблонного типа, то есть создающему отдельную функцию для нуля, одного, двух параметров[6].
Удалены из языка
Любые операции между enum и дробным; enum и другим enum. Ошибкоопасное наследие Си. Запрещены в Си++20, операция «звездолёт» <=> никогда не разрешалась[7]. Использовать явное преобразование типов. Может помешать совместимости с Си, обходится легко: +C1+C2.
Функции больше не могут возвращать ссылку на временный объект[8]. На именованный стековый пока ещё могут. Поведение is_convertible_v<int,constdouble&> не изменяется — константа остаётся true, ведь преобразование int→constdouble& законно в других местах.
Некодируемые строковые литералы (например, из-за отсутствия конкретного символа в кодировке исполнения) теперь ошибочны[9]. Многосимвольные литералы 'abc' не могут иметь префикса кодировки, и могут состоять только из символов, укладывающихся в один байт (или другую кодовую единицу) каждый.
Уничтожение объекта недоопределённого типа (classX;) операцией delete, даже без запрета (неофициально запрещён большинством компиляторов)[10]. Менеджер памяти знает размер выделенного участка и ему не нужна информация о типе. Но раньше предполагалось, что деструктор ничего не делает, что может снижать взаимозависимость между единицами трансляции в настоящем, но если в будущем тривиальный объект станет управляемым, будет утечка памяти.
Сравнение массивов. Они сравнивались как указатели, и с операцией «звездолёт» (Си++20) были запрещены[11]. Обходится легко: +a==b, нужно редко.
Диагностика доступа до инициализации
Доступ до инициализации — это известный источник ошибок, и теперь запрещён в очень ограниченном виде — только на стеке[12]. Если действительно неопределённое значение нужно — использовать новый атрибут [[indeterminate]].
union всегда считается инициализированным полностью. На объекты в «куче» диагностика не распространяется.
voidh(){intd1,d2;inte1=d1;// теперь ошибкаinte2=d1;// теперь ошибкаassert(e1==e2);// OKassert(e1==d1);// выполнялось, теперь ошибкаassert(e2==d1);// выполнялось, теперь ошибкаstd::memcpy(&d2,&d1,sizeof(int));// OK, но у d2 теперь ошибочное значениеassert(e1==d2);// выполнялось, теперь ошибкаassert(e2==d2);// выполнялось, теперь ошибка}voidf(int);voidg(){intx[[indeterminate]],y;f(y);// ошибкаf(x);// неопределённое поведение}
Запрещены в библиотеке
is_trivial. Использовать конкретные особенности типа: если нам важно тривиальное конструирование и уничтожение, но не важно присваивание — значит, is_trivially_constructible&&is_trivially_destructible[13].
Само определение тривиального типа сложное: хотя бы один действующий (то есть не исключённый явно через =delete или неявно) конструктор/операция копирования/переноса, все они, если действуют, то тривиальны. Также тривиальный конструктор без параметров и тривиальный деструктор.
Удалены из библиотеки
Весь заголовок <codecvt> — нет обработки ошибок[14]. Запрещён в Си++17. Использовать внешние, более управляемые функции.
allocator<T>::is_always_equal[15]. Ошибкоопасен при наследовании от аллокатора, в котором этот is_always_equal есть. Запрещён в Си++20, для проверки возможностей аллокатора использовать allocator_traits. Использовать в собственных аллокаторах, когда это действительно играет роль.
string.reserve() без параметров, эквивалентный reserve(0)[16]. Со старым API строк (Си++98…17) использовалось как shrink_to_fit, им же и заменено. В Си++20 reserve больше не укорачивает строку, а данную перегрузку запретили.
strstream (поток, который пишет в буфер памяти) — запрещён давно в Си++98 из-за опасности переполнения буфера[17]. Использовать spanstream (Си++20).
wstring_convert (преобразование кодировок из многобайтовой в Юникод, заголовок <locale>) — запрещено в Си++17 из-за сложности[18][19].
Атомарный API shared_ptr — запрещён в Си++20, использовать atomic[20].
Снят запрет
polymorphic_allocator.destroy — запрещено в Си++20. Пусть это же можно сделать и через allocator_traits, так короче[21].
Язык
Разные изменения в языке
Параметром-значением в шаблонах (non-type template parameter) может стать и вызов конструктора. Указано, когда такой вызов возможен, а когда нет[22].
Теперь можно навешивать атрибуты и на структурные переменные: auto[a,b[[vendor::attribute]],c]=f();[23]. Предложенное назначение — аннотирование кода для углублённой проверки на безопасность: например, char* в данном месте не требует закрывающего нуля.
Структурные переменные в условных операторах сами могут быть условием, если для структуры в целом существует надлежащее преобразование в bool: if(auto[first,last]=parse())[24]. Или, вместе с новыми изменениями в библиотеке: if(auto[ptr,ec]=std::to_chars(p,last,42)){}[25].
Конструируемые строки в static_assert
Для начала придумали понятие «не вычисляемая строка» (unevaluated string): закавыченная строка, значение которой не проходит в скомпилированную программу, а нужно только компилятору. Они являются частью _Pragma, asm, [[nodiscard]]… — и, конечно, static_assert[26]. Им запрещается иметь префикс кодировки.
Впоследствии позволили в static_assert любую константно вычисляемую строку[27]:
// Былоtemplate<typenameT,autoExpected,unsignedlongSize=sizeof(T)>constexprboolensure_size(){static_assert(sizeof(T)==Expected,"Неожиданный sizeof");returntrue;}static_assert(ensure_size<S,1>());// Остаётся надеяться, что компилятор напишет, что дело было в ensure_size<int, 1, 4>
// Сталоstatic_assert(sizeof(S)==1,std::format("Неожиданный sizeof: хотел 1, получил {}",sizeof(S));// Неожиданный sizeof: хотел 1, получил 4
constexprformat намеренно не внесён, но его прообраз, библиотека libfmt, уже способна на constexpr.
i-й элемент пакета параметров
Теперь его можно получить как T...[i]. Например: voidf(T&&...t){g(std::forward<T...[0]>(t...[0]));}[28].
Формально это несколько бьёт по имеющемуся коду: voidf(T...[0]){} представляло собой пакет безымянных массивов, но по факту не покрыто тестовыми программами и даже не компилировалось в MSVC и G++. C# и D поддерживают и i-й параметр с конца, но отрицательные числа для этого ошибкоопасны, а более сложный синтаксис решено не просить.
Эта функциональность, написанная на шаблонах, даёт O(n) специализаций[29]. В CLang, а за ним и в G++ реализовано «волшебным» (встроенным в компилятор) шаблоном __type_pack_element<i,Types...> и используется, например, в variant.
Имя _ может повторяться
auto[where,_]=insert(); — давно устоявшаяся манера программирования, когда функция возвращает два поля, а нужно одно, особенно если возвращается неговорящий тип вроде pair. Второй вариант — когда нужен именованный (не временный) объект, но имя не важно: захват мьютексаlock_guard_(someMutex). На случай, когда таких подчерков несколько, идиому расширили:[30]
namespacea{auto_=f();auto_=f();// Остаётся ошибка: с глобальными переменными не работает}int_;voidf(){using::_;// Остаётся OK, добавление в пространство имён постороннего символаauto_=42;// Теперь OKusing::_;// Остаётся ошибка: using _ разрешено только до локальной _auto_=0;// Теперь OKstaticint_;// Остаётся ошибка: со статическими переменными не работает{auto_=1;// Остаётся OK, замещениеassert(_==1);// Остаётся OK, имеем дело с замещённой переменной}assert(_==42);// Ошибка: которая из двух?}
Использование или неиспользование имени в этом контексте не должно вызывать предупреждений.
Для функций, типов, usingX=Y, концепций и шаблонных параметров новый механизм бесполезен: этим объектам либо нужно говорящее имя, либо Си++ уже даёт подходящие механизмы вроде безымянных типов.
Расширен constexpr
Преобразование указателей в void*, а потом обратно в свой тип[31]. Преобразование в посторонние типы неконстантно. Используется для так называемого стирания типа — при выполнении информация хранится в переменной общего типа, но её обработка выстраивается так, что все преобразования в частный тип верны. (Так устроены, например, обобщённые типыJava.) В CLang механизм уже есть (потребовался для выделения памяти) и вынести наружу ничего не стоит, G++ и EDG не видят препятствий. По заявлениям Г. Саттера, это шаг к constexprformat[32].
Предыдущее изменение привело к тому, что теперь можно сделать constexpr placement new, допустимый только если указатель действительно смотрит на свой тип, и являющийся простой инициализацией[33]. Воспользовавшись нововведением, перенесли в constexpr библиотеку неинициализированной памяти (Си++17).
Constexpr-указатели, ссылки и структурные переменные, представляющие собой просто название по имени того или иного constexpr-объекта[34].
Выброс исключений с последующей обработкой[35]. Но в любом случае авария не должна выпадать наружу, иначе это не constexpr: вычисляется при исполнении, если контекст позволяет, и ошибка — если нет. Раньше уже факт выброса снимал constexpr. Некоторым наиболее распространённым исключениям сделан constexprwhat().
Вариативный friend
Одно из назначений оператора friend — объекты-утилиты, сделанные через саморекурсивные шаблоны. Если шаблон вариативный, то друзей может быть много.
Пример: так называемый passkey, идиома Си++, используемая, если скрытую функцию надо вызвать из несвязанного шаблона (обычно make_unique/make_shared). Чтобы шаблон имел к ней доступ, функция должна быть общедоступной, и скрывают не её, а параметр-затычку, так называемый passkey, который так просто не получишь.
// Вариативный passkeytemplate<class...Ts>classPasskey{friendTs...;Passkey(){}};classC{public:// Можно вызвать только из Blarg, Blip и Bazvoidintentional(Passkey<Blarg,Blip,Baz>);};// Раскрыть класс для внутренних объектовtemplate<class...Ts>structVS{template<classU>friendclassC<Ts>::Nested...;};
Разрешение вариативных шаблонных перегрузок с концепциями
Для простой шаблонной перегрузки с концепциями 1-2 уже прописано: если подходят несколько шаблонных функций, брать ту, чьи ограничения сильнее. То же самое сделано и для вариативной 3-4, очень сложным языком. «Почти правильный» код Си++23 может перестать компилироваться в 26[36].
Иногда нужно отказаться от автоматического присваивания, одной из унаследованных функций или нежелательного преобразования типа. В Си++03 функцию удаляют заголовком без тела, по возможности скрытым private:voidf();. В Си++11 появилось тело =delete: компилятор, а не линкер явно сообщает о недопустимом вызове. По словам источника, «автор библиотеки говорит: „Я знаю, что вы думаете делать, и это неверно“».
Нововведение дополнительно сообщает программисту, почему функция удалена и что делать — «…и это неверно, и я скажу, почему неверно и как надо». Например: Proxy<T>factory(constT&&)=delete("Опасно висячими ссылками");[37]. Другие приведённые в источнике причины: старый API выброшен и отсылает на новый, некопируемый/труднокопируемый тип, недопустимое конструирование строки из nullptr, неправильный синтаксис создания динамического массива функцией make_unique.
Существуют предложения сделать условный =delete, как это сделали с explicit(bool) (Си++20) и noexcept(bool) (Си++11), но, по заверениям заявки, данный синтаксис не бросит на это тень.
Пакеты в структурных переменных
Синтаксический сахар для сложных шаблонов, разбирающих объект-кортеж на части[38]. Это работало и раньше — только на уровне библиотеки.
auto[x,y,z]=f();// остаётся OKauto[...xs]=f();// новоеauto[x,...rest]=f();// тоже новоеtemplate<classP,classQ>autodot_product(Pp,Qq){auto&&[...p_elems]=p;auto&&[...q_elems]=q;return(...+(p_elems*q_elems));}
Редакционные правки
Разрешены разночтения в лексическом анализаторе: сращиванием строк текста через \⤶ и склеиванием лексем через препроцессорное ## можно получить имя символа; переводы строк внутри закавыченной строки запрещены. Это статус-кво, поддерживаемый G++, CLang и EDG[39].
Некодируемые строковые литералы (например, из-за отсутствия конкретного символа в кодировке исполнения) ошибочны[9].
Уточнены правила игнорирования стандартных атрибутов[40]:
Стандартный атрибут должен быть корректным по правилам текущего Си++, даже если игнорируется. (Уже в Си++23[32] и только добавлено примечание.)
У стандартных атрибутов необязательная семантика: убирание атрибута из корректной программы может менять её внешнее поведение, но не может придумывать новое — лишь ограничить до одного из допустимых вариантов, когда атрибут есть, и, возможно, убрать какие-то компилятороспецифичные гарантии. (Также в Си++23 и добавлено примечание.)
Псевдофункция препроцессора__has_cpp_attribute должна проверять, реагирует ли компилятор на данный атрибут (а не разбирает ли) — а если разбирает, но не реагирует, атрибут бесполезен и макросы совместимости должны развёртываться во внутренние функции вроде __builtin_assume. (А это новое правило.)
Объявлено, что объект initializer_list ссылается на опорный массив, который может появиться в памяти двумя способами: как временный объект или как ссылка на какой-то массив, чьё время жизни продлено[41]. Другими словами, нет нужды копировать из сегмента данных на стек, теряя в производительности и надёжности.
Требования к generate_canonical переписаны так, чтобы работало на недвоичных машинах, сохранялись статистические свойства на всём диапазоне [0,1) — и результирующее число никогда из-за недостатков дробной арифметики не стало бы единицей[42]. В результате может нарушиться повторяемость — на том же генераторе случайных битов могут выходить другие дробные.
Переписано, когда можно опускать скобки при агрегатной инициализации: Pointx[2]={1,2,3,4};[43].
Заголовок модуля exportmoduleName; не может быть макросом — это усложняет его обработку системой сборки[44]. Импорт может — не вызывает таких сложностей.
Выкинуты все [[nodiscard]] из стандарта в отдельный документ, описывающий оптимальную практику, в каких случаях его применять[46]. Предполагается, что изменения в этот документ будут вноситься легче, чем в стандарт. Один пример: правило MISRA C++ 28.6.4 запрещает вызывать как процедуры remove[_if], unique и empty[47] — на empty аннотация была, чтобы не путали с clear, а на остальных не было (результат нужен в дальнейшем resize/erase).
Упрощены грамматические правила для литералов[48].
Уточнена работа операций сравнения в expected[49].
На стыке диапазонов, алгоритмов и разрешения перегрузки в пространствах имён возник специфичный вид объектов, призванных не вызывать функции из <algorithm> — ниблоиды (niebloids), в честь Эрика Ниблера, автора библиотеки диапазонов. Реализованы Ниблером в изначальной библиотеке, подхвачены G++, CLang и Microsoft, и их узаконили[50].
Гармонизация с Си
В набор символов внесены остатки печатного ASCII@$`, которые могут пригодится впоследствии[51]. Ранее в Си23 добавили @$, в первую очередь из-за EBCDIC — оба символа в разных диалектах кодировки на разных позициях[52].
Выкинут strtok из автономной библиотеки вслед за Си[53], так как содержит внутреннее состояние. Большинство реализаций используют потоколокальные переменные, которые в автономной среде могут отсутствовать.
Переписан макрос assert, чтобы лучше поддерживались шаблоны и многомерная индексация, коих просто не существовало на момент появления препроцессора Си[54].
Новые библиотеки Си23 stdbit.h и stdckdint.h, без Си++-аналогов <cstdbit/cstdckint>[55].
Библиотека
Разные изменения в библиотеке
Простейшая[к 1] библиотека идентификации кодировки исполнения[56].
Получение системного дескриптора из fstream[57]. Может использоваться в высоконадёжном программировании, когда надо гарантированно записать данные на диск[58].
Поддержка отладчика. Новый заголовочный файл <debugging> с тремя функциями: breakpoint(), breakpoint_if_debugging(), boolis_debugger_present()[59].
Теперь объект ignore применим не только в tie: std::ignore=foo();[60].
Автономная библиотека
Автономная (freestanding) библиотека не полагается на системные вызовы (даже выделение памяти), выброс исключений (требует серьёзной работы со стеком), может быть написана даже на чистом Си++ и потому полностью кроссплатформенна.
Возможен (не обязателен) operatornew, возвращающий nullptr, приводящий к системной аварии или делающий что угодно по желанию реализатора. Добавлен макрос __cpp_lib_has_default_operator_new, проверяющий, возможно ли выделение памяти — например, вместо динамического std::vector могут использоваться массивы ограниченного размера[61].
Множество функций Си, включая строковые и математические, а также <charconv> и char_traits[62].
algorithm,array,optional,variant,string_view[63]. Переписаны монадные функции optional так, чтобы не ссылались на неавтономный (выбрасывающий исключения) value.
constevalboolis_within_lifetime(&union_.field) — «волшебная» (реализованная внутри компилятора) функция, проверяющая, держит ли union то или иное поле[66]. Тип union при компиляции изначально (с Си++11) помеченный на манер variant, Си++20 позволил менять активное поле при компиляции, а доступ к другому полю отключает constexpr. Используется для экстремальной оптимизации по памяти с сохранением константности — например, для однобайтового optional<bool>.
from_chars_result получил operatorbool[71] — проверку кода ошибки.
to_string для дробных выдаёт то же, что и format("{}",x). А он, в свою очередь, то же, что to_chars — в компактном точном нелокализованном виде[58][к 2]. Ранее он был унифицирован с printf("%f",x), то есть обращался к глобальной локали (ненадёжно, да и вычисление нужных параметров локали затратно)[58] и плохо работал со слишком большими/малыми числами[72]. Это нарушение совместимости, но to_string значительно реже других методов перевода чисел в строку. Проверив случайные 100 вызовов, авторы обнаружили, что только семь из них дробные, в одном явная ошибка — запись в INI в локализованном виде, а остальные используются для отладки.
stringstream можно инициализировать строками string_view[73].
string+string_view[75]. Изначально в операции отказали из-за особенностей архитектуры LLVM — всё, что можно, она исполняет «лениво», и appendточками следования фиксирует, где исполнять, а сложение в большом выражении может выйти за время жизни string_view. Так что целых пять редакций — это попытка найти наиболее удачную реализацию.
Параметры ширины теперь также проверяются при компиляции[77].
Форматирование строк, заранее не известных: 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 принимается только по временной ссылке[78].
Серьёзная ошибка, ранее случившаяся в fmt (прообразе format): кодовые единицы char, будучи отформатированы как числа или с «широкой» форматной строкой, выдавали зависящий от реализации вид[80]. Теперь char, отформатированный как число, будет unsigned; отформатированный как символ в широком контексте — символом с кодом 0…255.
print может захватывать или не захватывать мьютекс консоли в зависимости от того, как происходит преобразование: преобразовать в строку целиком, потом вывести (например, для чисел), или параллельно преобразование-вывод (например, для массивов)[83].
Добавлена copyable_function, построенная по принципу новой move_only_function (Си++23) и значительно более лёгкая[к 1], чем function (Си++11). Последнюю всё-таки решили не запрещать[84].
Добавлена совсем лёгкая function_ref, не инкапсулирующая вызываемый объект, а просто ссылающаяся на него[85]. Может использоваться для callback’ов, если основная функция тяжёлая и не хочется делать её шаблонной. Std::function (Си++11) тоже годится на эту роль, но он один из самых тяжёлых типов STL. Класс писали своими силами: в заявке приведены шесть реализаций, некоторые на Си++14, и три из них назывались function_ref.
Добавлен облегчённый шаблонный карринг через bind_front, если вызываемый объект (например, слотQt) вычисляется раз и навсегда при компиляции[86].
This-параметры из Си++23 позволили внести одну из перегрузок visit внутрь variant[87].
std::is_virtual_base_of — важно при преобразовании указателей из типа в тип[88]. Приведён пример: в зависимости от того, виртуальный целевой указатель или нет, weak_ptr переносится из типа в тип через сильный указатель или напрямую.
Добавлен weak_ptr.owner_hash и несколько других подобных функций[91].
Закончен разнородный поиск в [unordered_]set/map: добавлены шаблонные insert, insert_or_assign, try_emplace, operator[], bucket[92]. Разнородный поиск начат в Си++14, и позволяет хранить с «тяжёлыми» ключами (string), а искать по «лёгким» (string_view или даже constchar*). Программист сам включает разнородный поиск (полем-типом CompareObject::is_transparent) и задаёт набор допустимых ключей.
Возможность писать std::find(v.begin(),v.end(),{3,4});[94]. Для этого всего лишь в шаблоны типа template<classT,classAllocator,classU> добавили classU=T, которое работает, когда тип ключа определить невозможно.
inplace_vector — простейший массив переменной длины
Массив переменной, но ограниченной длины, основанный на обычном массиве[95]. Этот контейнер часто пишут собственными силами — скажем, boost::static_vector<T,Capacity>. Нужен, если даже обычный вектор слишком тяжёлый, или менеджер памяти недоступен (в автономной/безопасной среде, на очень ограниченных машинах). Constexpr, если внутренний тип тривиальный. Тривиально копируемый, если внутренний тип тривиально копируемый.
Частично автономный: часть функций при переполнении массива выбрасывает исключения. Но такие структуры любят в ограниченных средах, безопасном и системном программировании[96], где исключениями пользоваться не принято, так что есть функции вроде try_emplace_back.
indirect и polymorphic — аналоги unique_ptr
Представляют собой указатели единоличного доступа. Семантически это объекты-значения, с такими отличиями от старого unique_ptr:
есть конструктор копирования, копирующий объект;
const-доступ делает константным и объект;
для удобства могут и не содержать объекта, и для этого есть функция, именуемая valueless_after_move, но эта семантика не поощряется;
может применяться оптимизация малых буферов.
Разница только в том, что indirect поддерживает только свой тип (и годится, например, для идиомы pimpl), а polymorphic — любой производный, и потому «под капотом» содержит инфраструктуру для подбора нужного конструктора копирования[97].
Диапазоны и другие представления данных
Переписан projected (внутренний тип библиотеки диапазонов), лучше работающий с указателями на недоопределённые классы (classOpaque;). Многие из функций диапазонов не работали там, где работал «голый» STL[98].
ranges::generate_random[102] — стандартная версия простейшая, но авторы библиотек могут добавлять к генераторам/распределениям нестандартные функции, чтобы получать сразу много случайных чисел. Какие именно — стандарта пока нет.
Объекту std::optional даны итератор, begin и end — то есть он тоже стал диапазоном[103].
Выкинуто invocable<F&,iter_common_reference_t> из многих концепций, связанных с итераторами, что позволило итераторы-заместители (vector<bool>)[104].
Впоследствии сделали объект для излишнего выравнивания — aligned_acccessor[110].
Улучшено угадывание статических (устанавливающихся при компиляции) габаритов mdspan, если таковые имеются[111].
std::mdspan<float,std::dextents<2>>a; — не столько для краткости, сколько для угадывания шаблонных параметров: mdspana(storage.data(),height,width);[112].
Параллельное программирование
Атомарный API
atomic_fetch_min/max — вычисление минимума/максимума атомарной переменной и обычной, и запись полученного обратно в атомарную[113].
atomic_ref может ссылаться на cv-объект. Предполагаемое назначение — объект в системной памяти и у него семантика volatile, а для доступа между потоками одной программы нужен atomic[114].
atomic_ref может давать указатель на неатомарный объект[97]. Заявленные задачи: старый API на volatile, отход от атомарного доступа к неатомарному, атомарный доступ к полю объекта, а не ко всему объекту вместе.
Примитив неблокирующей синхронизации. Объект хранится в динамической памяти. Как только этот объект изменили, создают новый такой же, а старый, когда можно, удаляют[115].
// Было — блокирующая версияData*data_;std::shared_mutexm_;template<typenameFunc>autoreader_op(Funcfn){std::shared_lock<std::shared_mutex>l(m_);Data*p=data_;returnfn(p);}voidupdate(Data*newdata){Data*olddata;{std::unique_lock<std::shared_mutex>wlock(m_);olddata=std::exchange(data_,newdata);}deleteolddata;}
// Стало — не блокируются только читателиstd::atomic<Data*>data_;template<typenameFunc>autoreader_op(Funcfn){std::scoped_lockl(std::rcu_default_domain());Data*p=data_;returnfn(p);}voidupdate(Data*newdata){Data*olddata=data_.exchange(newdata);std::rcu_synchronize();deleteolddata;}
Главный недостаток идиомы read-copy-update в данном исполнении — не ждут только читатели, писатель может надолго «зависать». Это «зависание» означает, что другие читатели работают и держат объект, но не всегда допустимо.
Hazard pointer дополнительно следит, какие потоки пользуются тем или иным объектом, и как только объект перестаёт использоваться, он исчезает[116].
Идиома похожа на подсчёт ссылок, но подсчитывает только локальные ссылки из функций доступа — а не глобальные ссылки между объектами. Это позволяет циклические ссылки без слежения, чей «ранг» выше (от «контейнеров» к «содержимому» — shared_ptr, в прочие стороны — weak_ptr), а также без присущего shared/weak_ptrуправляющего объекта, исчезающего, когда исчезнет последний слабый указатель.
Система сделана беспрепятственной по записи ценой повышенного расхода памяти: read-copy-update хранит одно поколение старых данных, а hazard pointer — сколько угодно[58].
Поскольку G++ всё ещё держит совместимость двоичных интерфейсов, на будущие дополнения оставили 4/8 байтов на объект.
(Старая блокирующая версия — та же)
// Стало — не блокируется и писательstructData:std::hazard_pointer_obj_base<Data>{}std::atomic<Data*>pdata_;template<typenameFunc>autoreader_op(FuncuserFn){std::hazard_pointerh=std::make_hazard_pointer();Data*p=h.protect(pdata_);returnuserFn(p);}voidwriter(Data*newdata){Data*old=pdata_.exchange(newdata);old->retire();}
Фреймворк асинхронно-параллельного исполнения
Предполагается, что немалые части этой библиотеки будут написаны не на Си++. Два главных объекта — планировщик (scheduler) и задача на исполнение (sender), оба — концепции (senderauto). Для тех, кто сам пишет планировщики, есть объект receiver для этой же задачи[117].
usingnamespacestd::execution;schedulerautosch=thread_pool.scheduler();senderautobegin=schedule(sch);senderautohi=then(begin,[]{std::cout<<"Hello world! Have an int.";return13;});senderautoadd_42=then(hi,[](intarg){returnarg+42;});auto[i]=this_thread::sync_wait(add_42).value();
Антон Полухин из Яндекса считает, что пока у этой системы есть недостатки: она устроена на шаблонах и концепциях (нет единого полиморфного объекта для передачи между библиотеками), и крайняя низкоуровневость[118].
Стандартная работа беззнаковых типов — арифметика остатков: при переходе через значение превращается в 0. Знаковые — зависят от реализации. Но это не всегда нужно: например, может означать «сколько угодно» и прибавление к нему единицы должно оставлять . Никакой защиты от дурака нет. Поддерживаются четыре арифметических действия и преобразование типов. Деление с упором div_sat при делении на ноль перестаёт быть константным[121].
#include<numeric>// Считаем, что у нас 8-битный char и отрицательные в дополнительном кодеintx1=add_sat(3,4);// 7intx2=sub_sat(INT_MIN,1);// INT_MINunsignedcharx3=add_sat(255,4);// 3!! — работа в int и преобразование 259 → 3unsignedcharx4=add_sat<unsignedchar>(255,4);// 255unsignedcharx5=add_sat(252,x3);// Ошибка, нет нужной перегрузкиunsignedcharx6=add_sat<unsignedchar>(251,x2);// 251!! — преобразование INT_MIN → 0unsignedcharx7=saturate_cast<unsignedchar>(-5);// 0
BLAS — известный стандарт линейной алгебры, мало менявшийся с годами.
Это такой же путь к интеграции в Си++ сторонних стандартов, как Юникод (идёт работа) и часовые пояса.
Конструкция полностью шаблонная и на mdspan. Преимущества перед стандартным BLAS:
Работают любые типы, в том числе смешанные (данные в float, расчёт в double), а не только четыре стандартных BLAS’овских.
Можно оптимизировать работу с матрицами небольших жёстко заданных габаритов — например, через SIMD.
С небольшими изменениями возможно будет запустить целый пакет заданий (например, для машинного обучения).
Пока вне рассмотрения: расширенные функции BLAS/LAPACK, разреженная алгебра, расчёты повышенной точности, тензоры («матрицы» с тремя и более измерениями), параллельная работа, перегрузка операций ±. Последняя — из-за неоднозначности (есть несколько типов умножения векторов), данные могут быть в одном типе, а работа в другом, и из-за больших объёмов памяти и многоступенчатых расчётов промежуточные буфера часто используются повторно.
Поиск, как надо повернуть вектор в 2D, чтобы одна из координат равнялась нулю (поворот Гивенса)
Разные виды норм векторов и матриц
Операции y := Ax, y := Ay, z := y + Ax для матриц общего вида, а также симметричных/эрмитовых/треугольных
Операции A:=A + xyᵀ + yxᵀ, A:=A + αxxᵀ для симметричных/эрмитовых матриц (для эрмитовых матриц — вместо транспонирования соответственно эрмитово сопряжение)
Операции A := xyᵀ, C := BA, C:=E + BA для матриц общего вида
Операции C := BA, C := AC, C := CA, C:=E + BA для симметричных/эрмитовых/треугольных матриц
Операция с симметричной/эрмитовой матрицей C :=C + αAAᵀ (матрица C была симметричной/эрмитовой и в результате ею останется, матрица A общего вида)
Операция с симметричной/эрмитовой матрицей C:=C + ABᵀ + BAᵀ
Решение треугольной СЛАУ, а также серии таких СЛАУ с общей матрицей
Нет даже решения заполненных СЛАУ. Вот одна из стандартных функций — решение треугольной СЛАУ на месте.
Здесь пустой тип-тэг Triangle показывает, каким треугольником собрана матрица, верхним или нижним. Аналогичный тэг DiagonalStorage — что представляет собой диагональ матрицы A: явные значения или неявные единицы. В векторе b изначально правая часть системы, в результате расчёта будет решение.
Семейство инкрементальных генераторов псевдослучайных чисел Philox
В параллельных расчётах сложно получить псевдослучайность: если брать несколько независимых генераторов (multistream approach), то чем их инициализировать? Можно также брать 2-е, 12-е, 22-е число (substream approach)[123], но арифметический генератор потребует 10 пусков на каждое число. В таких случаях используют специальные инкрементальные (основанные на счётчике) генераторы псевдослучайных чисел — к счётчику (единицы машинных слов) прибавляется 1, затем обрабатывается очень слабым шифром[124]. Потоки либо получают каждый по генератору с далёкими друг от друга значениями счётчика[124] в уверенности, что последовательности не пересекутся (multistream approach), либо берут 2-е, 12-е, 22-е число без потери производительности (substream approach)[123].
В Си++ добавлено семейство инкрементальных генераторов Philox (2011)[125], и две специализации philox4x32 и philox4x64. Поведение каждой жёстко заспецифицировано: 10 000-й запуск версии 4×32 даст число 1 955 073 260. Семейство широко распространено и независимо реализовано у NumPy[124], nVidia, AMD, Intel, Microsoft…
#embed — инициализация массива данных двоичным файлом[127], прошла в Си23.
Примитивы хранения данных:
Улей — специализированный менеджер памяти для однотипных данных, используемый в играх и скоростной торговле. Никогда не перемещает, объект вставляется в случайное место, относительно быстры операции «проход», «добавление» и «удаление»[128].
path_view, аналог string_view для путей[129]. По факту variant, способный ссылаться без хранения на пути разных форматов и оперативно перекодировать в системный вид — rendered_path, буфер достаточно больших размеров с возможностью запросить ещё больше, выделив память.
«Волокна», элементы стековойкооперативной многозадачности[131]. Сопрограммы Си++20 бесстековые, то есть могут использоваться в любой среде, где есть setjmp/longjmp и выделение памяти (в автономной нет даже их).
Некое pattern matching — возможно, используя ключевое слово inspect, аналог switch, действующий даже на разные объектные подтипы и разные шаблоны строк[135]
Комментарии
↑ 12Здесь и далее «лёгкий/тяжёлый» — по системным ресурсам (процессорному коду, расходу памяти и т. д.), «простой/сложный» — по работе программиста, «простейший» — по функциональности.
↑Компактный — выбирает простой или стандартный вид в зависимости от того, что короче. Точный — производит достаточно цифр, чтобы обратное преобразование вернуло ту же дробь до бита. Нелокализованный — набор цифр всегда ASCII, знак отрицательного числа дефис-минус, разделитель дроби точка, разделителя тысяч нет.