Перейти к содержанию

C++26

Материал из Википедии — свободной энциклопедии

C++26 или C++2c (латиницей), или Си++26 (кириллицей) — ожидаемая редакция языка программирования C++. Разработка началась сразу же после того, как в феврале 2023 года зафиксировали C++23.

С самого начала стандарт получил рабочее имя «Си++26». Си++0x должен был приблизить устаревающий Си++ к современным языкам, непрерывно разрабатываемым под руководством единоличника. (Си++ разрабатывается комитетом и есть много реализаций — в отличие от, например, Python.) Но стандарт запоздал, и с версии 14 новый язык выпускают не «когда готово», а раз в три года, при этом последний год — только доводка. КОВИД не сместил расписание — к пандемии как раз была готова версия 20, а версию 23 подготовили дистанционно.

  1. 12…16 июня 2023, Варна (Болгария)[1] — первое после пандемии КОВИДа очное заседание.
  2. 6…11 ноября 2023, Каилуа-Кона (Гавайи, США)[2]
  3. 18…23 марта 2024, Токио (Япония)[3].
  4. 24…29 июня 2024, Сент-Луис (Миссури, США)[4]
  5. 18…23 ноября 2024, Вроцлав (Польша)[5]
  6. 10…15 февраля 2025, Хагенберг-им-Мюлькрайс (Австрия)
  7. Июнь 2025, София (Болгария) — ожидается
  8. Ноябрь 2025, Каилуа-Кона (Гавайи, США) — ожидается

Запрещены и удалены

[править | править код]

Запрещены в языке

[править | править код]

Переменные параметры Си без оксфордской запятой

[править | править код]

Запрещён void f(int x...); — старый редкий синтаксис переменных параметров Си на разборе стека через va_list[6]. Остаются добавленный позже Си-совместимый void f(int x, ...);, неявный шаблон Си++20 void f(auto... x);

Синтаксис шаблонов и va_list также можно объединить void f(auto... x...); или void f(auto......);, это также запрещено. Остаётся void f(auto..., ...); Такая функция вполне работоспособна, хоть и мало на что нужна.

Мотивация тройная:

  • Шаг к шаблонному синтаксису «сколько угодно параметров int», то есть создающему отдельную функцию для нуля, одного, двух параметров. Этот синтаксис постоянно просят[7].
  • Ошибкоопасность — вместо неявного шаблона Си++20 void g(auto ...args); легко написать функцию из одного шаблонного параметра, за которым следуют переменные void g(auto args...);;
  • Бессмысленность — с широкой шаблонной функциональностью единственное место, где ещё нужен разбор стека,— собственно совместимость с Си. А ведь этот синтаксис совсем не Си-совместимый.

Удалены из языка

[править | править код]
  • Любые операции между enum и дробным; enum и другим enum. Ошибкоопасное наследие Си. Запрещены в Си++20, операция «звездолёт» <=> никогда не разрешалась[8]. Использовать явное преобразование типов. Может помешать совместимости с Си, обходится легко: +C1 + C2.
  • Функции больше не могут возвращать ссылку на временный объект[9]. На именованный стековый пока ещё могут, хоть это тоже ошибка и диагностируется компиляторами. Константа is_convertible_v<int, const double&> остаётся true, ведь преобразование int const double& законно в других местах. По мнению Антона Полухина (Яндекс), такое половинчатое решение связано с неявным преобразованием типа — ревизору не сразу понятно, был ли создан временный объект из именованного[10]. Само понятие временного объекта сложное и подразумевает операции вроде взятия поля, которые никогда не сделают из временного объекта именованный или динамический.
  • Некодируемые строковые литералы (например, из-за отсутствия конкретного символа в кодировке исполнения) теперь ошибочны[11]. Многосимвольные литералы 'abc' не могут иметь префикса кодировки, и могут состоять только из символов, укладывающихся в одну минимальную кодовую единицу (байт). Для низкоуровневой оптимизации обработки текста лучше использовать bit_cast.
  • Уничтожение объекта недоопределённого типа (class X;) операцией delete, даже без запрета (неофициально запрещён большинством компиляторов)[12]. Менеджер памяти знает размер выделенного участка и ему не нужна информация о типе. Но раньше предполагалось, что деструктор ничего не делает, что может снижать взаимозависимость между единицами трансляции в настоящем, но если в будущем тривиальный объект станет управляемым, будет утечка памяти.
  • Сравнение массивов. Они сравнивались как указатели, с операцией «звездолёт» (Си++20) запретили[13]. Обходится легко: +a == b (Си), std::to_address(a) == b (Си++), нужно редко.

Диагностика доступа до инициализации

[править | править код]

Доступ до инициализации — это известный источник ошибок, и теперь запрещён в очень ограниченном виде — только на стеке[14]. Если действительно неопределённое значение нужно — использовать новый атрибут [[indeterminate]].

union всегда считается инициализированным полностью. На объекты в «куче» диагностика не распространяется.

void h() {
  int d1, d2;

  int e1 = d1;           // теперь ошибка
  int e2 = d1;           // теперь ошибка

  assert(e1 == e2);      // OK
  assert(e1 == d1);      // выполнялось, теперь ошибка
  assert(e2 == d1);      // выполнялось, теперь ошибка

  std::memcpy(&d2, &d1, sizeof(int)); // OK, но у d2 теперь ошибочное значение
  assert(e1 == d2);      // выполнялось, теперь ошибка
  assert(e2 == d2);      // выполнялось, теперь ошибка
}

void f(int);

void g() {
  int x [[indeterminate]], y;
  f(y);     // ошибка
  f(x);     // неопределённое поведение
}

Запрещены в библиотеке

[править | править код]
  • is_trivial и вообще понятие тривиального типа[15] — из-за обманчивости: с одной стороны, может показаться, что тип тривиален по всем статьям, с другой — столь жёсткое требование часто не нужно. Тривиальный тип — это тип, который тривиально конструируется без параметров и тривиально копируемый, и редко нужны оба требования сразу.
    • Понятие «тривиально копируемый» оставлено, хоть и сложное: возможно копирование хотя бы одним из четырёх способов — конструктором/операцией, копирования/переноса, все доступные способы тривиальны (сводятся к memcpy); также тривиальный (ничего не делающий) деструктор.
  • memory_order::consume — специфичная работа компилятора и кэша в атомарных переменных, предназначенная для задачи: один поток заполняет переменную данными, второй их потребляет (отсюда название), и более поздние загрузки этой переменной не могут быть вынесены наперёд. К посторонним переменным никаких требований. В Си++17 был устно запрещён, в Си++20 вернули. На отдельных архитектурах (POWER, GPGPU, старые реализации ARM) все загрузки будут consume, но не все — acquire, и на них, по-видимому, лучше использовать платформо-специфичные «хаки» — а в других платформах компиляторы не проверяли зависимостей и ставили барьер типа acquire (никакой доступ к памяти не может быть вынесен наперёд). Теперь всегда consume == acquire[16]. Свойство volatile в Java также, по сути, acquire. Аннотация [[carries_dependency]] (Си++11) осталась, но больше ничего не делает.

Удалены из библиотеки

[править | править код]
  • Весь заголовок <codecvt> — нет обработки ошибок[17]. Запрещён в Си++17. Использовать внешние, более управляемые функции.
  • allocator<T>::is_always_equal[18]. Ошибкоопасен при наследовании от аллокатора, в котором этот is_always_equal есть. Запрещён в Си++20, для проверки возможностей аллокатора использовать allocator_traits. Использовать в собственных аллокаторах, когда это действительно играет роль.
  • string.reserve() без параметров, эквивалентный reserve(0)[19]. Со старым API строк (Си++98…17) использовалось как shrink_to_fit, им же и заменено. В Си++20 reserve больше не укорачивает строку, а данную перегрузку запретили.
  • strstream (поток, который пишет в буфер памяти) — запрещён давно в Си++98 из-за опасности переполнения буфера[20]. Использовать spanstream (Си++20).
  • wstring_convert (преобразование кодировок из многобайтовой в Юникод, заголовок <locale>) — запрещено в Си++17 из-за сложности[21][22].
  • Атомарный API shared_ptr — запрещён в Си++20, использовать atomic[23].

Снят запрет

[править | править код]
  • polymorphic_allocator.destroy — запрещено в Си++20. Пусть это же можно сделать и через allocator_traits, так короче[24].

Разные изменения в языке

[править | править код]
  • Параметром-значением в шаблонах (non-type template parameter) может стать и вызов конструктора. Указано, когда такой вызов возможен, а когда нет[25].
  • Теперь можно навешивать атрибуты и на структурные переменные: auto [a, b [[vendor::attribute]], c] = f();[26]. Предложенное назначение — аннотирование кода для углублённой проверки на безопасность: например, char* в данном месте не требует закрывающего нуля.
  • Структурные переменные в условных операторах сами могут быть условием, если для структуры в целом существует надлежащее преобразование в bool[27]: if (auto [ptr, ec] = std::to_chars(p, last, 42)){} (вместе с изменениями в библиотеке)[28].
  • union, независимо от внутренних типов, тривиально конструируется-уничтожается — например, чтобы отвести место на неинициализированную память[29]. Такая неинициализированная память, если возможна, то проще, чем буфер постороннего типа alignas(T) char buf[N], и к тому же constexpr. Исключение: если одно из полей инициализировано по умолчанию, конструктор не тривиален, а если это поле ещё и снабжено нетривиальным деструктором — то деструктора нет.

Конструируемые строки в static_assert

[править | править код]

Для начала придумали понятие «невычисляемая строка» (unevaluated string): закавыченная строка, значение которой не проходит в скомпилированную программу, а нужно только компилятору. Они являются частью _Pragma, asm, [[nodiscard]]… — и, конечно, static_assert[30]. Автоматически считается, что они в единой для компилятора кодировке трансляции, покрывающей текущий и будущий Юникод, кроме, возможно, половинок суррогатных пар. Им запрещается иметь префикс кодировки, часть заэкранированных символов (нестандартные и однобайтовые).

Впоследствии позволили в static_assert любую константно вычисляемую строку[31]:

// Было
template <typename T, auto Expected, unsigned long Size = sizeof(T)>
constexpr bool ensure_size() {
  static_assert(sizeof(T) == Expected, "Неожиданный sizeof");
  return true;
}
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

Сам constexpr format намеренно не внесён, но его прообраз, библиотека libfmt, уже способна на constexpr.

i-й элемент пакета параметров

[править | править код]

Теперь его можно получить как T...[i]. Например: void f(T&&... t) { g(std::forward<T...[0]>(t...[0])); }[32].

Формально это несколько бьёт по имеющемуся коду: void f(T...[0]){} представляло собой пакет безымянных массивов, но по факту не покрыто тестовыми программами и даже не компилировалось в MSVC и G++. C# и D поддерживают и i-й параметр с конца, но отрицательные числа для этого ошибкоопасны, а более сложный синтаксис решено не просить.

Эта функциональность, написанная на шаблонах, даёт O(n) специализаций[33]. В CLang, а за ним и в G++ реализовано «волшебным» (встроенным в компилятор) шаблоном __type_pack_element<i, Types...> и используется, например, в variant.

Имя _ может повторяться

[править | править код]

auto [where, _] = insert(); — давно устоявшаяся манера программирования, когда функция возвращает два поля, а нужно одно, особенно если возвращается неговорящий тип вроде pair. Второй вариант — идиома RAII требует именованный (не временный) объект, но имя не используется: захват мьютекса lock_guard _(someMutex). На случай, когда таких подчерков несколько, идиому расширили:[34]

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 );  // Ошибка: которая из двух?
}

Правила языка получились простые.

  1. Заглушкой является переменная _ — локальная (automatic storage duration), неглобальная структурная, нестатическое поле, переменная-перехват в лямбда-функции. Действительно, для функций, типов, глобальных переменных, using X=Y, концепций и шаблонных параметров новый механизм бесполезен: этим объектам либо нужно говорящее имя, либо Си++ уже даёт подходящие механизмы вроде безымянных типов.
  2. Имя _ (независимо от того, годится ли объект на роль заглушки) можно использовать, пока не объявят подчерк-заглушку в том же блоке.
  3. Использование или неиспользование подчерка-заглушки не должно вызывать предупреждений.

Расширен constexpr

[править | править код]
  • Преобразование указателей в void*, а потом обратно в свой тип[35]. Преобразование в посторонние типы неконстантно. Используется для так называемого стирания типа — при выполнении информация хранится в переменной общего типа, но её обработка выстраивается так, что все преобразования в частный тип верны. (Так устроены, например, обобщённые типы Java.) В CLang механизм уже есть (потребовался для выделения памяти) и вынести наружу ничего не стоит, G++ и EDG не видят препятствий. По заявлениям Г. Саттера, это шаг к constexpr format[36].
  • Предыдущее изменение привело к тому, что теперь можно сделать constexpr placement new, допустимый только если указатель действительно смотрит на свой тип, и являющийся простой инициализацией[37]. Воспользовавшись нововведением, перенесли в constexpr библиотеку неинициализированной памяти (Си++17).
  • Constexpr-указатели, ссылки и структурные переменные, представляющие собой просто название по имени того или иного constexpr-объекта[38].
  • Выброс исключений с последующей обработкой[39]. Но в любом случае авария не должна выпадать наружу, иначе это не constexpr: вычисляется при исполнении, если контекст позволяет, и ошибка — если нет. Раньше уже факт выброса снимал constexpr. Некоторым наиболее распространённым исключениям сделан constexpr what(). Мотивация — при константных вычислениях иногда случаются ошибки, и нужно принудить компиляторы выводить в консоль диагностические сообщения, а не общее «выброшена ошибка std::invalid_argument».

Вариативный friend

[править | править код]

Одно из назначений оператора friend — объекты-утилиты, сделанные через саморекурсивные шаблоны. Если шаблон вариативный, то друзей может быть много.

Пример: так называемый passkey, идиома Си++, используемая, если скрытую функцию надо вызвать из несвязанного шаблона (обычно make_unique/make_shared). Чтобы шаблон имел к ней доступ, функция должна быть общедоступной, и скрывают не её, а параметр-затычку, так называемый passkey, который так просто не сконструируешь.

// Вариативный passkey
template<class... Ts>
class Passkey {
  friend Ts...;
  Passkey() {}
};

class C {
public:
  // Можно вызвать только из Blarg, Blip и Baz
  void intentional(Passkey<Blarg, Blip, Baz>);
};

// Раскрыть класс для внутренних объектов
template<class... Ts>
struct VS {
  template<class U>
  friend class C<Ts>::Nested...;
};

Разрешение вариативных шаблонных перегрузок с концепциями

[править | править код]

Для простой шаблонной перегрузки с концепциями 1-2 уже прописано: если подходят несколько шаблонных функций, брать ту, чья концепция сильнее (у́же). То же самое сделано и для вариативной 3-4, очень сложным языком. «Почти правильный» код Си++23 может перестать компилироваться в 26[40].

template <std::ranges::bidirectional_range R> void f(R&&); // №1
template <std::ranges::random_access_range R> void f(R&&); // №2

template <std::ranges::bidirectional_range... R> void g(R&&...); // №3
template <std::ranges::random_access_range... R> void g(R&&...); // №4

void call() {
    f(std::vector{1, 2, 3}); // OK, №2 сильнее
    g(std::vector{1, 2, 3}); // Теперь OK, №4 сильнее
}

=delete("причина")

[править | править код]

Иногда нужно отказаться от автоматического присваивания, одной из унаследованных функций или нежелательного преобразования типа. В Си++03 функцию удаляют заголовком без тела, по возможности скрытым private: void f();. В Си++11 появилось тело =delete: компилятор, а не линкер явно сообщает о недопустимом вызове. По словам заявки, «автор библиотеки говорит: „Я знаю, что вы думаете делать, и это неверно“». И в том, и в другом случае функция участвует в разрешении перегрузок.

Нововведение дополнительно сообщает программисту, почему функция удалена и что делать — «…и это неверно, и я скажу, почему неверно и как надо». Например: Proxy<T> factory(const T&&) = delete("Опасно висячими ссылками");[41]. Другие приведённые в источнике причины: старый API выброшен и отсылает на новый, некопируемый/труднокопируемый тип, недопустимое конструирование строки из nullptr, неправильный синтаксис создания динамического массива функцией make_unique.

Существуют предложения сделать условный =delete, как это сделали с explicit(bool) (Си++20) и noexcept(bool) (Си++11), но, по заверениям заявки, данный синтаксис не бросит на это тень.

Пакеты в структурных переменных

[править | править код]

Синтаксический сахар для сложных шаблонов, разбирающих объект-кортеж на части[42]. Это работало и раньше — только на уровне библиотеки.

auto [x,y,z] = f();  // обычная структурная переменная
auto [...xs] = f();  // новое
auto [x, ...rest] = f();  // тоже новое

// Новое: чтобы помножить кортеж на кортеж, оба рассматриваем
//  как структурные переменные
template <class P, class Q>
auto dot_product(P p, Q q) {
    auto&& [...p_elems] = p;
    auto&& [...q_elems] = q;
    return (... + (p_elems * q_elems));
}

Пока сделали минимально действующий продукт[43], с тремя операторами: предусловие, постусловие и самопроверка.

int f(const int x)
  pre (x != 1) // предусловие
  post (r : r != x) // постусловие
{
  contract_assert (x != 3); // самопроверка
  return x;
}

Пока отсутствуют:

  • Включение самопроверок в систему типов — пока самопроверки никак не изменяют тип функции.
  • Инварианты (одновременно предусловие и постусловие).
  • Возможность обратиться к исходным значениям переменных в постусловии.
  • Семантика, похожая на assume — компилятор предполагает, что условие выполняется, и оптимизирует код из этого предположения.
  • Более наглядный показ, что собой представляет запрограммированный алгоритм.
  • Деление самопроверок на уровни, которые можно включать и выключать по отдельности.
  • Постусловия для функций, из которых нет выхода, или есть выход только аварийный.
  • Самопроверки, которые исполнимы только при компиляции.
  • Сохранение состояния от одной самопроверки к другой — «функция вызывается только один раз».
  • Более сложные протоколы, чем вызов одной функции — например, перед работой с файлом нужно вызвать fopen.

Шаблоны на концепциях

[править | править код]

Проще всего объяснить примером[44]: понятие «диапазон целых» обобщается в «диапазон чего-то».

// Было
template<typename T>
  concept range_of_integrals = std::ranges::range<T>
       && std::integral<std::remove_cvref_t<std::ranges::range_reference_t<T>>>;

// Стало
template<typename T, template <typename...> concept C>
  concept range_of = std::ranges::range<T>
       && C<std::remove_cvref_t<std::ranges::range_reference_t<T>>>;
template<typename T>
  concept range_of_integrals = range_of<T, std::integral>;

Разработчики писали подобное на лямбда-функциях:

template <typename T, auto ConceptWrapperLambda>
  concept decays_to = requires {
    ConceptWrapperLambda.template operator()<std::decay_t<T>>();
  };
template <class T> requires decays_to<T, ([]<std::copyable>(){})>
auto f(T&& x) {}

Рефлексия при компиляции

[править | править код]

В июне 2025 года приняты целых семь статей[45].

Си++ получил две новых операции — «баян» и «кошачьи ушки», а также оператор template for.

Операция «кошачьи ушки» (метаинформация) constexpr auto mInt = ^^int; переводит объект Си++ (тип, поле…) в информацию о нём. Операция «баян» (материализация) [:mInt:] x = 1; — наоборот. Все операции с метаинформацией автоматически consteval (выполняются только при компиляции).

Оператор template for разворачивается в последовательность вызовов, и хорош и без метаинформации[46]:

struct S {};
long result = 0;
template for (auto x : s)
    result += sizeof(x);

С метаинформацией можно проводить разные преобразования, для этого есть std::meta, целая «волшебная» (определённая в компиляторе) библиотека.

Простейший пример — почленное хэширование. ^^T — получить метаинформацию о типе. nonstatic_data_members_of — есть метаинформация о типе, получить список полей. t.[:mem:] — «материализовать» метаинформацию в реальное поле объекта.

template <typename H, typename T> requires std::is_standard_layout_v<T>
void hash_append(H& algo, T const& t) {
  constexpr auto ctx = std::meta::access_context::unchecked();
  template for (constexpr auto mem : nonstatic_data_members_of(^^T, ctx)) {
      hash_append(algo, t.[:mem:]);
  }
}

Редакционные правки

[править | править код]
  • Закрыты разночтения в лексическом анализаторе: сращиванием строк текста через \⤶ и склеиванием лексем через препроцессорное ## можно получить имя символа; переводы строк внутри закавыченной строки запрещены. Это статус-кво, поддерживаемый G++, CLang и EDG[47].
  • Некодируемые строковые литералы (например, из-за отсутствия конкретного символа в кодировке исполнения) ошибочны[11].
  • Уточнены правила игнорирования стандартных атрибутов[48]:
    • Стандартный атрибут должен быть корректным по правилам текущего Си++, даже если игнорируется. (Уже в Си++23[36] и только добавлено примечание.)
    • У стандартных атрибутов необязательная семантика: убирание атрибута из корректной программы может менять её внешнее поведение, но не может придумывать новое — лишь ограничить до одного из допустимых вариантов, когда атрибут есть, и, возможно, убрать какие-то компилятороспецифичные гарантии. (Также в Си++23 и добавлено примечание.)
    • Псевдофункция препроцессора __has_cpp_attribute должна проверять, реагирует ли компилятор на данный атрибут (а не разбирает ли) — а если разбирает, но не реагирует, атрибут бесполезен и макросы совместимости должны развёртываться во внутренние функции вроде __builtin_assume. (А это новое правило.)
  • Объявлено, что объект initializer_list ссылается на опорный массив, который может появиться в памяти двумя способами: как временный объект или как ссылка на какой-то массив, чьё время жизни продлено[49]. Другими словами, нет нужды копировать из сегмента данных на стек, теряя в производительности и надёжности.
  • Требования к generate_canonical переписаны так, чтобы работало на недвоичных машинах, сохранялись статистические свойства на всём диапазоне [0,1) — и результирующее число никогда из-за недостатков дробной арифметики не стало бы единицей[50]. В результате может нарушиться взаимоповторяемость с Си++23 — на том же генераторе случайных битов могут выходить другие дробные.
  • Переписано, когда можно опускать скобки при агрегатной инициализации: Point x[2] = { 1, 2, 3, 4 };[51].
  • Заголовок модуля export module Name; не может быть макросом — это усложняет его обработку системой сборки[52]. Импорт может — не вызывает таких сложностей.
  • Пустой бесконечный цикл — больше не неопределённое поведение[53]. CLang в таких ситуациях почему-то исполнял посторонний код.
  • Выкинуты все [[nodiscard]] из стандарта в отдельный документ, описывающий оптимальную практику, в каких случаях его применять[54]. Предполагается, что изменения в этот документ будут вноситься легче, чем в стандарт. Один пример: правило MISRA C++ 28.6.4 запрещает вызывать как процедуры remove[_if], unique и empty[55] — на empty аннотация была, чтобы не путали с clear, а на остальных не было (результат нужен в дальнейшем resize/erase).
  • Упрощены грамматические правила для литералов[56].
  • Уточнена работа операций сравнения в expected[57].
  • На стыке диапазонов, алгоритмов и разрешения перегрузки в пространствах имён возник специфичный вид объектов, призванных не вызывать функции из <algorithm> — ниблоиды (niebloids), в честь Эрика Ниблера, автора библиотеки диапазонов. Реализованы Ниблером в изначальной библиотеке, подхвачены G++, CLang и Microsoft, и их узаконили[58].
  • Функции, работающие с непрерывными итераторами, получили официальное право преобразовывать их в указатели[59].
  • Объяснено, как atomic и atomic_ref могут работать с volatile-объектами. Первый — никак. Второй — только если структура объекта допускает это напрямую, без мьютекса: например, объект в системной памяти и у него семантика volatile, а для доступа между потоками одной программы нужен atomic[60].

Гармонизация с Си

[править | править код]
  • В набор символов внесены остатки печатного ASCII @$`, которые могут пригодится впоследствии[61]. Ранее в Си23 добавили @$, в первую очередь из-за EBCDIC — оба символа в разных диалектах кодировки на разных позициях[62].
  • Выкинут strtok из автономной библиотеки вслед за Си[63], так как содержит внутреннее состояние. Большинство реализаций используют потоколокальные переменные, которые в автономной среде могут отсутствовать.
  • Переписан макрос assert, чтобы лучше поддерживались шаблоны и многомерная индексация, коих просто не существовало на момент появления препроцессора Си[64].
  • Новые библиотеки Си23 stdbit.h и stdckdint.h, без Си++-аналогов <cstdbit/cstdckint>[65].

#embed — внедрение двоичных данных

[править | править код]

Очень часто нужно вставить в программу двоичные данные в форме «как есть» — заявка[66] упоминает «чистый» образ файловой системы, который надо развернуть, когда пользователь просит полный сброс устройства, PNG-иконку, встроенные в программу скрипты на другом языке. Разработчики на Turbo Pascal могут вспомнить программу binobj и функцию Graph.InstallUserDriver.

Конструкция constinit const unsigned char data[] = { 1, 2 }; создаёт непропорционально много лексем, и хороший синтаксический движок CLang как-то справляется с ней, а другие компиляторы могут «зависнуть» надолго: 4 мегабайта данных компилируются от 8 секунд (CLang) до минуты (MSVC), а внедряются — за долю секунды.

const unsigned char icon_display_data[] = {
    #embed "art.png"
};

Директива уже есть в стандарте Си23.

Преобразование данных из порядка байтов заведомо Intel/Motorola в машинный, важное для числовых таблиц (например, баллистических) не предусмотрено. Все примеры из заявки оперируют типом (unsigned) char, которому преобразование не нужно. Впрочем, немалое количество больших массивов — это не слова и более крупные единицы, а именно байтовые данные: PNG, UTF-8, исполняемый файл, образ диска

Библиотека

[править | править код]

Разные изменения в библиотеке

[править | править код]
  • Простейшая[к 1] библиотека идентификации кодировки исполнения[67].
  • Получение системного дескриптора из fstream[68]. Может использоваться в высоконадёжном программировании, когда надо гарантированно записать данные на диск[69].
  • Поддержка отладчика. Новый заголовочный файл <debugging> с тремя функциями: breakpoint(), breakpoint_if_debugging(), bool is_debugger_present()[70].
  • Теперь объект ignore применим не только в tie: std::ignore = foo();[71].
  • type_info из Си++23 получил кроссплатформенный порядок[72].

Автономная библиотека

[править | править код]

Автономная (freestanding) библиотека не полагается на системные вызовы (даже выделение памяти), выброс исключений (требует серьёзной работы со стеком), может быть написана даже на чистом Си++ и потому полностью кроссплатформенна.

  • Возможен (не обязателен) operator new, возвращающий nullptr, приводящий к системной аварии или делающий что угодно по желанию реализатора. Добавлен макрос __cpp_lib_has_default_operator_new, проверяющий, возможно ли выделение памяти — например, вместо динамического std::vector могут использоваться массивы ограниченного размера[73].
  • Множество функций Си, включая строковые и математические, а также <charconv> и char_traits[74].
  • algorithm, array, optional, variant, string_view[75]. Переписаны монадные функции optional так, чтобы не ссылались на неавтономный (выбрасывающий исключения) value.
  • expected, span, mdspan[76].
  • numeric, random[77]

Новые constexpr

[править | править код]
  • Устойчивая сортировка[78].
  • consteval bool is_within_lifetime(&union_.field) — «волшебная» (реализованная внутри компилятора) функция, проверяющая, держит ли union то или иное поле[79]. Тип union при компиляции изначально (с Си++11) помеченный на манер variant, Си++20 позволил менять активное поле при компиляции, а доступ к другому полю отключает constexpr. Используется для экстремальной оптимизации по памяти с сохранением константности — например, для однобайтового optional<bool>.
  • Больше математических функций, включая комплексные[80].
  • Библиотека неинициализированной памяти, в constexpr-контексте или ничего не делающая, или проводящая простое присваивание[81][82].
  • atomic, atomic_ref[83]. Многозадачности при компиляции, это, разумеется, не даст, но позволит выполнять такой код хоть в один поток.
  • Многие части стандартных контейнеров, включая deque и map[84].
  • Замена для integral_constant: тип constant_wrapper и шаблонная константа cw[85].
  • Немалое количество сценариев для format[86].

Перевод данных в строку и наоборот

[править | править код]
  • from_chars_result получил operator bool[87] — проверку кода ошибки.
  • to_string для дробных выдаёт то же, что и format("{}", x). А он, в свою очередь, то же, что to_chars — в компактном точном нелокализованном виде с целой частью[69][к 2]. Ранее он был унифицирован с printf("%f", x), то есть обращался к глобальной локали (ненадёжно, да и вычисление нужных параметров локали затратно)[69] и плохо работал со слишком большими/малыми числами[88]. Это нарушение совместимости, но to_string значительно реже других методов перевода чисел в строку. Проверив случайные 100 вызовов, авторы обнаружили, что только семь из них дробные, в одном явная ошибка — запись в INI в локализованном виде, а остальные используются для отладки.
  • stringstream можно инициализировать строками string_view[89].
  • То же самое с bitset[90].
  • string + string_view[91]. Изначально в операции отказали из-за особенностей архитектуры LLVM — всё, что можно, она исполняет «лениво», и append точками следования фиксирует, где исполнять, а сложение в большом выражении может выйти за время жизни string_view. Так что целых пять редакций — это попытка найти наиболее удачную реализацию.
  • string.subview — синтаксический сахар для получения подстроки как string_view[92]. Заявленная цель: роль обычного string’а сжалась до объекта промежуточного хранения, и часть функций string’а уже принимают string_view, но ни одна не производит. Ограничения на именованный (не временный) this решили не делать: string_view короткоживущ, буфер с данными почти всегда переживает его, а программист обычно ознакомлен с опасностями string_view и должен сам следить за временем жизни объектов.
  • Унифицировано форматирование указателей[93].
  • Параметры ширины теперь также проверяются при компиляции[94].
  • Форматирование строк, заранее не известных: 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 принимается только по временной ссылке[95].
  • В само́м make_format_args избавились от std::forward и временных объектов, делая форматирование более устойчивым к висячим ссылкам[96].
  • Серьёзная ошибка, ранее случившаяся в fmt (прообразе format): кодовые единицы char, будучи отформатированы как числа или с «широкой» форматной строкой, выдавали зависящий от реализации вид[97]. Теперь char, отформатированный как число, будет unsigned; отформатированный как символ в широком контексте — символом с кодом 0…255.
  • Форматирование path[98].
  • println() без параметров[99].
  • print может захватывать или не захватывать мьютекс консоли в зависимости от того, как происходит преобразование: преобразовать в строку целиком, потом вывести (например, для чисел), или параллельно преобразование-вывод (например, для массивов)[100].
  • Добавлена copyable_function, построенная по принципу новой move_only_function (Си++23) и много легче[к 1], чем function (Си++11), которая один из самых тяжёлых типов STL. Последнюю всё-таки решили не запрещать[101].
  • Добавлена совсем лёгкая function_ref, не инкапсулирующая вызываемый объект, а просто ссылающаяся на него[102]. Может использоваться для callback’ов, если основная функция тяжёлая или виртуальная, и не хочется делать её шаблонной. Класс писали своими силами: в заявке приведены шесть реализаций, некоторые на Си++14, и три из них назывались function_ref.
  • Добавлен облегчённый шаблонный карринг через bind_front, если вызываемый объект (например, слот Qt) вычисляется раз и навсегда при компиляции[103].
  • This-параметры из Си++23 позволили внести одну из перегрузок visit внутрь variant[104].
  • std::is_virtual_base_of — важно при преобразовании указателей из типа в тип[105]. Приведён пример: в зависимости от того, виртуальный целевой указатель или нет, weak_ptr переносится из типа в тип через сильный указатель или напрямую.
  • Объект monostate продублирован в <utility>[106]. Заявленных целей две: это тип со средним количеством функциональности — создание, копирование, сравнение — и его удобно применять для испытания собственных объектов хранения. Вторая — в библиотеке (например, в future) функциональность «ничего не возвращает» сделана через псевдотип void, но на такое способны разве что специалисты высшего класса, писавшие стандартную библиотеку. Среднему программисту проще это сделать через пустую структуру monostate.

Хранение данных

[править | править код]
  • Добавлен hash для календарных типов[107].
  • Добавлен weak_ptr.owner_hash и несколько других подобных функций[108].
  • Закончен разнородный поиск в [unordered_]set/map: добавлены шаблонные insert, insert_or_assign, try_emplace, operator[], bucket[109]. Разнородный поиск начат в Си++14, и позволяет хранить с «тяжёлыми» ключами (string), а искать по «лёгким» (string_view или даже const char*). Программист сам включает разнородный поиск (полем-типом CompareObject::is_transparent) и задаёт набор допустимых ключей.
  • Операции сравнения для reference_wrapper[110].
  • Возможность писать std::find(v.begin(), v.end(), {3, 4});[111]. Для этого всего лишь в шаблоны типа template<class T, class Allocator, class U> добавили class U=T, которое работает, когда тип ключа определить невозможно.
  • Кортежи: сравнение на равенство, обмен[112].

inplace_vector — простейший массив переменной длины

[править | править код]

Массив переменной, но ограниченной длины, основанный на обычном массиве[113]. Этот контейнер часто пишут собственными силами — скажем, boost::static_vector<T, Capacity>. Нужен, если даже обычный вектор слишком тяжёлый, или менеджер памяти недоступен (в автономной/безопасной среде, на очень ограниченных машинах). Constexpr, если внутренний тип тривиальный. Тривиально копируемый, если внутренний тип тривиально копируемый.

Частично автономный: часть функций при переполнении массива выбрасывает исключения. Но такие структуры любят в ограниченных средах, безопасном и системном программировании[114], где исключениями пользоваться не принято, так что есть функции вроде try_emplace_back.

indirect и polymorphic — аналоги unique_ptr

[править | править код]

Представляют собой указатели единоличного доступа. Семантически это объекты-значения, с такими отличиями от старого unique_ptr:

  • есть конструктор копирования, копирующий объект;
  • const-доступ делает константным и объект;
  • для удобства могут и не содержать объекта, и для этого есть функция, именуемая valueless_after_move, но эта семантика не поощряется и лучше null object и/или optional;
  • может применяться оптимизация малых буферов.

Разница только в том, что indirect поддерживает только свой тип (и годится, например, для идиомы pimpl), а polymorphic — любой производный, и потому «под капотом» содержит инфраструктуру для подбора нужного конструктора копирования[115].

Улей — неперемещающий динамический список

[править | править код]

Улей (hive) — специализированный менеджер памяти для однотипных данных, используемый в играх и скоростной торговле. Никогда не перемещает, объект вставляется в случайное место, быстры операции «проход», «добавление» и «удаление»[116].

Обёртка вокруг указателя[117], очень простой тип, который программисты часто пишут своими силами. Обычно применяется для передачи параметров в функцию, возвращается в результате поиска по набору[117]. Имеет преимущества перед указателем[118]:

  • Запрещает арифметику указателей, что даёт плюс к безопасности кода.
  • Сразу объясняет, что это за указатель: ссылка на объект или ничего. Владение объектом не передаётся, уничтожает тот, кто и раньше за него отвечал.
  • Позволяет все утилиты, имеющиеся в optional «из коробки»: итератор, монадные функции…

Диапазоны и другие представления данных

[править | править код]
  • Переписан projected (внутренний тип библиотеки диапазонов), лучше работающий с указателями на недоопределённые классы (class Opaque;). Многие из функций диапазонов не работали там, где работал «голый» STL[119].
  • Комплексным числам добавлено get<0> и <1>, как обычным кортежам (tuples)[120].
  • basic_const_iterator можно получить из неконстантного собрата[121].
  • views::concat[122].
  • ranges::generate_random[123] — стандартная версия простейшая, но авторы библиотек могут добавлять к генераторам/распределениям нестандартные функции, чтобы получать сразу много случайных чисел. Какие именно — стандарта пока нет.
  • Объекту std::optional даны итератор, begin и end — то есть он тоже стал диапазоном[124].
  • Выкинуто invocable<F&, iter_common_reference_t> из многих концепций, связанных с итераторами, что позволило итераторы-заместители (vector<bool>)[125].
  • views::cache_latest[126].
  • ranges::reserve_hint — обычно применяется для представлений (views), и приближённо прикидывает, сколько там элементов. Связано это с Юникодом: малая доля букв имеет верхний регистр, и совсем немного — верхний регистр не из одной буквы, так что для условного uppercase_view можно предположить, что ожидаемая длина — это длина исходной строки (а может, немного больше).[127].
  • views::to_input — переводит представление в самый простой вид, чтобы дальнейшие алгоритмы не пытались реализовать продвинутую функциональность, видя, например, представление прямого доступа[128].
  • views::indices — немалое количество случаев вызова объекта iota начинается с нуля. И тут злую шутку играет x64: размеры объектов — unsigned long long, и iota(0, x.size()) не компилируется. Если для нешаблонного кода можно написать 0uz, то для шаблонного обходной путь довольно сложен[129].

span (Си++20) и mdspan (Си++23)

[править | править код]
  • Функция submdspan, производящая слайсинг многомерных массивов. На выходе получается mdspan (Си++23), возможно, с нестандартным типом внутри[130].
  • Конструктор span(initalizer_list), не требующий промежуточного объекта вроде массива[131]. Время жизни некоторых initializer_list ограничено, и программисты, работающие со string_view и span, привыкли проверять его.
  • span.at(i), выкидывающий аварию[132].
  • mdspan с излишним выравниванием[133].
    • Впоследствии сделали объект для излишнего выравнивания — aligned_accessor[134].
  • Улучшено угадывание статических (устанавливающихся при компиляции) габаритов mdspan, если таковые имеются[135].
  • std::mdspan<float, std::dextents<2>> a; — не столько для краткости, сколько для угадывания шаблонных параметров: mdspan a(storage.data(), height, width);[136]

Параллельное программирование

[править | править код]
  • Параллельные операции на диапазонах[137].

Атомарный API

[править | править код]
  • atomic_fetch_min/max — вычисление минимума/максимума атомарной переменной и обычной, и запись полученного обратно в атомарную[138]. Заявленные задачи: бывает аппаратно (POWER) и высокоуровневая реализация неоптимальна, существует в библиотеках вроде OpenCL, част как алгоритм.
  • atomic_ref может давать указатель на неатомарный объект[139]. Заявленные задачи: старый API на volatile; отход от атомарного доступа к неатомарному; атомарный доступ к полю объекта, а не ко всему объекту вместе; адрес вообще не надо разыменовывать (например, чтобы различать объекты).

Примитив неблокирующей синхронизации. Объект хранится в динамической памяти. Как только этот объект изменили, создают новый такой же, а старый, когда можно, удаляют[140].

// Было — блокирующая версия
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 дополнительно следит, какие потоки пользуются тем или иным объектом, и как только объект перестаёт использоваться, он исчезает[141].

Идиома похожа на подсчёт ссылок, но подсчитывает только локальные ссылки из функций доступа — а не глобальные ссылки между объектами. Это позволяет циклические ссылки без слежения, чей «ранг» выше (от «контейнеров» к «содержимому» — shared_ptr, в прочие стороны — weak_ptr), а также без присущего shared/weak_ptr управляющего объекта, исчезающего, когда исчезнет последний слабый указатель.

Система сделана беспрепятственной по записи ценой повышенного расхода памяти: read-copy-update хранит одно поколение старых данных, а hazard pointer — сколько угодно[69].

Поскольку 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();
}

Фреймворк асинхронно-параллельного исполнения

[править | править код]

Предполагается, что немалые части этой библиотеки будут написаны не на Си++. Два главных объекта — планировщик (scheduler) и задача на исполнение (sender), оба — концепции (sender auto). Для тех, кто сам пишет планировщики, есть объект receiver для этой же задачи[142].

using namespace std::execution;

scheduler auto sch = thread_pool.scheduler();

sender auto begin = schedule(sch);
sender auto hi = then(begin, []{
    std::cout << "Hello world! Have an int.";
    return 13;
});
sender auto add_42 = then(hi, [](int arg) { return arg + 42; });

auto [i] = this_thread::sync_wait(add_42).value();

Антон Полухин из Яндекса считает, что пока у этой системы есть недостатки: она устроена на шаблонах и концепциях (нет единого полиморфного объекта для передачи между библиотеками), и крайняя низкоуровневость[143].

В ноябре 2024 добавили объекты prop и env[144].

В июне 2025 добавили первую параллельную реализацию[145].

Математика

[править | править код]

Арифметика с насыщением (упором в край)

[править | править код]

Стандартная работа беззнаковых типов — арифметика остатков: при переходе через значение превращается в 0. Знаковые — зависят от реализации. Но это не всегда нужно: заявка упоминает расчёт цветов (RGB) в компьютерной графике, когда непойманное переполнение даёт простые 255. Никакой защиты от дурака нет. Поддерживаются четыре арифметических действия и преобразование типов. Деление с упором div_sat при делении на ноль перестаёт быть константным[147].

# 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
unsigned char x7 = saturate_cast<unsigned char>(-5);  // 0

Заполненная линейная алгебра

[править | править код]

Добавились BLAS-подобные алгоритмы линейной алгебры для заполненных (большей частью ненулевых) векторов и матриц[148]. Мотивация[148]:

  • Комитет Си++ сам поставил линейную алгебру приоритетом.
  • Си++ — стандартная платформа для наукоёмкого ПО, которому линейная алгебра более чем нужна.
  • Это как сортировка массива: примитивные алгоритмы медленные, а самые быстрые реализации можно получить аппаратно-специфичными улучшениями.
  • В стандарте Си++ и так много разной математики — и умножение матриц не менее важно, чем функции Бесселя.
  • BLAS — известный стандарт линейной алгебры, мало менявшийся с годами.
  • Это такой же путь к интеграции в Си++ сторонних стандартов, как Юникод (идёт работа) и часовые пояса.

Конструкция полностью шаблонная и на mdspan. Преимущества перед стандартным BLAS:

  • Работают любые типы, в том числе смешанные (данные в float, расчёт в double), а не только четыре стандартных BLAS’овских.
  • Можно оптимизировать работу с матрицами небольших жёстко заданных габаритов — например, через SIMD.
  • С небольшими изменениями возможно будет запустить целый пакет заданий (например, для машинного обучения).

Пока вне рассмотрения: расширенные функции BLAS/LAPACK, разреженная алгебра, расчёты повышенной точности, тензоры («матрицы» с тремя и более измерениями), параллельная работа, перегрузка операций ±. Последняя — из-за неоднозначности (есть несколько типов умножения векторов), данные могут быть в одном типе, а работа в другом, и из-за больших объёмов памяти и многоступенчатых расчётов промежуточные буфера часто используются повторно.

Добавлены[148]:

  • Простейшие операции с матрицами вроде сложения
  • Поиск, как надо повернуть вектор в 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ᵀ
  • Решение треугольной СЛАУ, а также серии таких СЛАУ с общей матрицей

Нет даже решения заполненных СЛАУ. Вот одна из стандартных функций — решение треугольной СЛАУ на месте.

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 изначально правая часть системы, в результате расчёта будет решение.

Семейство инкрементальных генераторов псевдослучайных чисел Philox

[править | править код]

В параллельных расчётах сложно получить псевдослучайность: если брать несколько независимых генераторов (multistream approach), то чем их инициализировать, чтобы не случилась атака «дней рождения», приводящая к плохому перемешиванию точек? Можно также брать 2‑е, 12‑е, 22‑е число (substream approach)[149], но арифметический генератор потребует 10 пусков на каждое число. В таких случаях используют специальные инкрементальные (основанные на счётчике) генераторы псевдослучайных чисел — к счётчику (единицы машинных слов) прибавляется 1, затем обрабатывается очень слабым шифром[150]. Потоки либо получают каждый по генератору с далёкими друг от друга значениями счётчика[150] в уверенности, что последовательности не пересекутся (multistream approach), либо берут 2‑е, 12‑е, 22‑е число без потери производительности (substream approach)[149].

В Си++ добавлено семейство инкрементальных генераторов Philox (2011)[151], и две специализации philox4x32 и philox4x64. Поведение каждой жёстко заспецифицировано: 10 000-й запуск версии 4×32 даст число 1 955 073 260. Семейство широко распространено и независимо реализовано у NumPy[150], nVidia, AMD, Intel, Microsoft…

Сложная долго разрабатывавшаяся библиотека[152]. Пример:

float * addr = ...;
void f(std::simd<double>x ) {
  x.copy_to(addr, std::simd_flag_convert |
            std::simd_flag_overaligned<16 >);
}

Работа с неопределённым поведением

[править | править код]

observable — барьер для неопределённого поведения

[править | править код]

Функция observable является барьером для неопределённого поведения[153] — всё, что до неё, если само не является неопределённым, должно пройти нормально. Например, она может служить для автотестов на неопределённое поведение, или как барьер перед сомнительным действием.

void b(int &r, int *p) {
  if (!p) std::fprintf(stderr, "count: %d\n", ++r);
  std::observable();
  if (!p) std::fprintf(stderr, "p is null\n");
  *p += r;    // Из-за этой строки компилятор может считать, что p ≠ null
}

Функция не обязательно «магическая» (встроенная в компилятор); одна из возможных реализаций — доступ к volatile-переменной. На многозадачность никак не влияет и не заменяет межпоточную синхронизацию.

Не принято, но ожидается: любая проверка контракта — вызов этой же std::observable[154].

Укреплённая библиотека

[править | править код]

Си++ много думает над тем, как сделать новый код безопаснее, но надо налаживать безопасность здесь и сейчас, тем более доклад Белого дома (февраль 2024) говорит, что Си++ — очень опасный язык. Предлагается новая версия стандартной библиотеки — укреплённая. Некоторые (пока немногие) предусловия, обычно выход за диапазон, превращаются в нарушение контракта[155].

Может быть вопрос: а зачем это, если и без контрактов будет аварийный останов? Может и не быть, а тихо испортить память или выдавать другие ошибки: разыменование пустого std::optional может выдать что угодно[156].

В последний момент (ноябрь 2025) добавлено: нарушения укреплённой библиотеки должны вызывать аварийный останов на месте[157] — программа уже в некорректном состоянии.

  • Библиотечная поддержка сопрограмм (языковая есть в Си++20)
  • Сеть — не удалось сделать модульный подход
  • Некое pattern matching — возможно, оператор inspect, аналог switch, действующий даже на разные объектные подтипы и разные шаблоны строк[158]
  • Структуры данных
    • path_view, аналог string_view для путей[159]. По факту variant, способный ссылаться без хранения на пути разных форматов и оперативно перекодировать в системный вид — rendered_path, буфер достаточно больших размеров с возможностью запросить ещё больше, выделив память.
  • Многозадачность:
    • Параллельные очереди[160].
    • «Волокна», элементы стековой кооперативной многозадачности[161]. Сопрограммы Си++20 бесстековые, то есть могут использоваться в любой среде, где есть setjmp/longjmp и выделение памяти (в автономной нет даже их).
  • Прочее:
    • Улучшения в библиотеке диапазонов[162].

Переезжающие типы

[править | править код]

Переезд объекта — полное разрушение объекта до неинициализированной памяти и создание идентичного в неинициализированном блоке — важная функция любого ABI, используемая, например, при «подчистке» временных объектов, возврате значения функции. Компилятор старается избавляться от переездов, создавая объект там, где его ожидает программа-потребитель, но это не всегда возможно.

Си++98 создавал копию, уничтожал имеющийся. Крайняя непроизводительность этого механизма и сделала оптимизацию возврата, никак не стандартизированную, но существующую почти во всех компиляторах. Перемещение объектов (Си++11) подняло производительность в важнейших случаях — тип T&& говорит, что объект ненужный и можно забрать из него ценные ресурсы (выделенную память, файловые дескрипторы и т. д.), оставляя объект опустелым, но корректным — ведь потом заработает деструктор[156]. Так что резервы не исчерпаны: замечено, что многие нетривиальные объекты могут менять дислокацию значительно проще, чем конструктором перемещения и деструктором. И действительно, немало скоростных библиотек вроде RapidJson сами пишут ускоренный переезд.

Две новых концепции, связанные с производительностью[163]:

  • Тривиально переезжающий тип — объект достаточно перебросить в неинициализированную память бит в бит, а исходный бросить, не вызывая деструктор — и это будет корректный объект.
  • Заменяемый тип — вместо уничтожения-создания достаточно вызвать операцию перемещения.

Эти понятия ортогональны: динамический массив (std::vector) с полиморфным выделением памяти только тривиально переезжающий (замена даст новые данные во владении старого менеджера памяти); строка, содержащая указатель на внутренний малый буфер, только заменяемая (указатель на внутренние поля не переезжает). И оба не тривиально конструируемые с перемещением.

Автоматически (без явного указания программистом) тривиально переезжают типы с заведомо тривиальными конструктором перемещения и деструктором (виртуальный деструктор и раньше снимал тривиальность!) — правда, компилятор и в Си++98 знал, что делать. В тех случаях, которые важны для производительности, компилятор не в состоянии определить эти свойства, и программист пишет «тривиально переезжающий (заменяемый) тип, если возможно» — компилятор проверяет, что все поля переезжают/заменяются, и делает таковой всю структуру.

struct Y trivially_relocatable_if_eligible {};
static_assert(std::is_trivially_relocatable_v<Y>);

В библиотеку неинициализированной памяти (Си++17) добавили функцию std::relocate — переезд объекта полным или сокращённым образом.

В последний момент (ноябрь 2025) выброшены из-за трудноустранимой проблемы[164]. На некоторых архитектурах даже указатели не способны тривиально переезжать, что улучшает информационную безопасность, но приводит к непоследовательному поведению, а также терминологическому конфузу — «тривиальное» в Си++ всегда было «эквивалентное memcpy». Кроме того, важное место для переезда — обёртки со стиранием типа, когда непонятно, есть ли внутри такие непереезжающие указатели, и остаётся по старинке вызывать конструктор-деструктор. Если переезд и вернётся позже — то с подписью «зависит от реализации»[164].

Комментарии

[править | править код]
  1. 1 2 Здесь и далее «лёгкий/тяжёлый» — по системным ресурсам (процессорному коду, расходу памяти и т. д.), «простой/сложный» — по работе программиста, «простейший» — по функциональности.
  2. Компактный — выбирает простой или стандартный вид в зависимости от того, что короче (сторонние функции форматирования, рассчитанные на человекочитаемость, могут тяготеть к простому виду). С целой частью — не может выдавать запись .5. Точный — производит достаточно цифр, чтобы обратное преобразование вернуло ту же дробь до бита. Нелокализованный — жёстко закодирована так называемая «локаль Си»: набор цифр ASCII 0…9, знак отрицательного числа ASCII дефис-минус, разделитель дроби точка, разделителя тысяч нет.

Примечания

[править | править код]
  1. Five Awesome C++ Papers for the H1 2023 — C++26, Varna and More — C++ Stories. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  2. Trip report: Autumn ISO C++ standards meeting (Kona, HI, USA) – Sutter’s Mill. Дата обращения: 16 ноября 2023. Архивировано 16 ноября 2023 года.
  3. https://isocpp.org/files/papers/N4961.pdf
  4. Источник. Дата обращения: 30 июня 2024. Архивировано 12 августа 2024 года.
  5. Upcoming Meetings, Past Meetings : Standard C. Дата обращения: 6 марта 2024. Архивировано 7 сентября 2020 года.
  6. P3176R0: The Oxford variadic comma. Дата обращения: 25 ноября 2024. Архивировано 30 ноября 2024 года.
  7. Homogeneous variadic function parameters
  8. Источник. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  9. Disallow Binding a Returned Glvalue to a Temporary
  10. C++26 — прогресс и новинки от ISO C++ / Хабр
  11. 1 2 Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  12. Источник. Дата обращения: 13 сентября 2024. Архивировано 1 сентября 2024 года.
  13. Источник. Дата обращения: 25 ноября 2024. Архивировано 20 ноября 2024 года.
  14. Erroneous behaviour for uninitialized reads. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  15. P3247R2: Deprecate the notion of trivial types. Дата обращения: 25 ноября 2024. Архивировано 1 декабря 2024 года.
  16. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3475r1.pdf
  17. Источник. Дата обращения: 29 ноября 2023. Архивировано 15 ноября 2023 года.
  18. Источник. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  19. Источник. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  20. Remove Deprecated strstreams From C++26. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  21. std::wstring_convert — cppreference.com. Дата обращения: 23 марта 2024. Архивировано 9 марта 2024 года.
  22. Источник. Дата обращения: 24 июня 2024. Архивировано 24 июня 2024 года.
  23. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2869r3.pdf
  24. Источник. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  25. P2308R0: Template parameter initialization. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  26. Источник. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  27. Structured binding declaration as a _condition_ - HackMD. Дата обращения: 30 июня 2024. Архивировано 30 июня 2024 года.
  28. What’s new in C++26 (part 1). Дата обращения: 19 ноября 2024. Архивировано 15 ноября 2024 года.
  29. trivial unions (was std::uninitialized<T>)
  30. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  31. Источник. Дата обращения: 29 августа 2023. Архивировано 29 августа 2023 года.
  32. Источник. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  33. Источник. Дата обращения: 2 июля 2024. Архивировано 5 мая 2024 года.
  34. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  35. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  36. 1 2 Trip report: Summer ISO C++ standards meeting (Varna, Bulgaria) — Sutter’s Mill. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  37. constexpr placement new. Дата обращения: 30 июня 2024. Архивировано 30 июня 2024 года.
  38. Источник. Дата обращения: 25 ноября 2024. Архивировано 20 ноября 2024 года.
  39. D3068R5: Allowing exception throwing in constant-evaluation. Дата обращения: 25 ноября 2024. Архивировано 27 ноября 2024 года.
  40. Источник. Дата обращения: 30 июня 2024. Архивировано 30 июня 2024 года.
  41. P2573R2: = delete(«should have a reason»). Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  42. Structured Bindings can introduce a Pack. Дата обращения: 25 ноября 2024. Архивировано 1 декабря 2024 года.
  43. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2900r13.pdf
  44. https://isocpp.org/files/papers/P2841R7.pdf
  45. Trip report: June 2025 ISO C++ standards meeting (Sofia, Bulgaria) – Sutter’s Mill
  46. https://eel.is/c++draft/stmt.expand
  47. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  48. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  49. P2752R2: Static storage for braced initializers. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  50. A new specification for std::generate_canonical. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  51. Clarifying rules for brace elision in aggregate initialization. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  52. P3034R1: Module Declarations Shouldn’t be Macros. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  53. P2809R3: Trivial infinite loops are not Undefined Behavior. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  54. Remove nodiscard annotations from the standard library specification. Дата обращения: 30 июня 2024. Архивировано 30 июня 2024 года.
  55. Источник. Дата обращения: 8 августа 2024. Архивировано 8 августа 2024 года.
  56. Источник. Дата обращения: 25 ноября 2024. Архивировано 30 ноября 2024 года.
  57. https://isocpp.org/files/papers/P3379R1.html
  58. Retiring niebloids
  59. Converting contiguous iterators to pointers
  60. cv-qualified types in atomic and atomic_ref
  61. Add @, \$, and \` to the basic character set. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  62. N 2701: @ and $ in source and execution character set. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  63. P2937R0: Freestanding: Remove strtok. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  64. Make assert() macro user friendly for C and C. Дата обращения: 17 ноября 2023. Архивировано 17 ноября 2023 года.
  65. P3370R1: Add new library headers from C23. Дата обращения: 25 ноября 2024. Архивировано 20 ноября 2024 года.
  66. P1967R14: #embed - a scannable, tooling-friendly binary resource inclusion mechanism
  67. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  68. Native handles and file streams. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  69. 1 2 3 4 Первые новинки C++26: итоги летней встречи ISO / Хабр. Дата обращения: 14 сентября 2023. Архивировано 5 сентября 2023 года.
  70. Debugging Support. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  71. Make std::ignore a first-class object. Дата обращения: 30 июня 2024. Архивировано 26 июня 2024 года.
  72. Constexpr Type Ordering
  73. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2013r5.html Архивная копия от 28 июля 2023 на Wayback Machine.
  74. P2338R4: Freestanding Library: Character primitives and the C library. Дата обращения: 8 августа 2023. Архивировано 10 августа 2023 года.
  75. P2407R5: Freestanding Library: Partial Classes. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  76. P2833R2: Freestanding Library: inout expected span. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  77. P2976R1: Freestanding Library: algorithm, numeric, and random
  78. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  79. Checking if a union alternative is active. Дата обращения: 31 июля 2023. Архивировано 21 июля 2023 года.
  80. Источник. Дата обращения: 31 июля 2023. Архивировано 30 июля 2023 года.
  81. P3369R0: constexpr for uninitialized_default_construct. Дата обращения: 25 ноября 2024. Архивировано 20 ноября 2024 года.
  82. P3508R0: Wording for "constexpr for specialized memory algorithms". Дата обращения: 25 ноября 2024. Архивировано 2 декабря 2024 года.
  83. P3309R3: constexpr atomic<T> and atomic_ref<T>. Дата обращения: 25 ноября 2024. Архивировано 3 декабря 2024 года.
  84. P3372R2: constexpr containers
  85. `std::constant_wrapper`
  86. constexpr std::format
  87. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  88. P2587R3: to_string or not to_string. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  89. Источник. Дата обращения: 31 июля 2023. Архивировано 21 июля 2023 года.
  90. Источник. Дата обращения: 31 июля 2023. Архивировано 21 июля 2023 года.
  91. P2591R5: Concatenation of strings and string views
  92. https://wg21.link/P3044
  93. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  94. Type-checking format args. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  95. P2918R1: Runtime format strings II. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  96. P2905R1: Runtime format strings. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  97. P2909R4: Fix formatting of code units as integers
    (Dude, where’s my char?)
    . Дата обращения: 23 марта 2024. Архивировано 1 марта 2024 года.
  98. P2845R6: Formatting of std::filesystem::path. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  99. Источник. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  100. P3107R3: Permit an efficient implementation of std::print. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  101. Источник. Дата обращения: 31 июля 2023. Архивировано 21 июля 2023 года.
  102. function_ref: a type-erased callable reference — HackMD. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  103. Bind front and back to NTTP callables — HackMD. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  104. Member visit. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  105. P2985R0: A type trait for detecting virtual base classes. Дата обращения: 30 июня 2024. Архивировано 30 июня 2024 года.
  106. Источник. Дата обращения: 25 ноября 2024. Архивировано 1 декабря 2024 года.
  107. P2592R3: Hashing support for std::chrono value classes. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  108. P1901R2 — Enabling the Use of weak_ptr as Keys in Unordered Associative Containers. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  109. P2363R5: Extending associative containers with the remaining heterogeneous overloads. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  110. Comparisons for reference_wrapper. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  111. P2248R8: Enabling list-initialization for algorithms. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  112. https://isocpp.org/files/papers/P1789R3.pdf
  113. `inplace_vector` - HackMD. Дата обращения: 30 июня 2024. Архивировано 30 июня 2024 года.
  114. Новости РГ21 — группы по стандартизации C++ | Антон Полухин, Яндекс — YouTube
  115. https://isocpp.org/files/papers/P3019R12.pdf
  116. Introduction of std::hive to the standard library
  117. 1 2 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2988r12.pdf
  118. Встреча ISO C++ в Софии: С++26 и рефлексия / Хабр
  119. P2538R1: ADL-proof std::projected. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  120. Источник. Дата обращения: 14 ноября 2023. Архивировано 10 октября 2023 года.
  121. `std::basic_const_iterator` should follow its underlying type’s convertibility. Дата обращения: 14 ноября 2023. Архивировано 15 ноября 2023 года.
  122. `views::concat`. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  123. Источник. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  124. Give std::optional range support - HackMD. Дата обращения: 30 июня 2024. Архивировано 30 июня 2024 года.
  125. Removing the common reference requirement from the indirectly invocable concepts. Дата обращения: 30 июня 2024. Архивировано 30 июня 2024 года.
  126. views::cache_latest. Дата обращения: 25 ноября 2024. Архивировано 29 ноября 2024 года.
  127. https://isocpp.org/files/papers/P2846R6.pdf
  128. views::to_input
  129. Add std::views::indices(n) - HackMD
  130. Submdspan. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  131. P2447R4: std::span over an initializer list. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  132. span.at() — HackMD. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  133. Padded mdspan layouts. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  134. aligned_accessor: An mdspan accessor expressing pointer over-alignment
  135. Better mdspan's CTAD. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  136. dextents Index Type Parameter. Дата обращения: 30 июня 2024. Архивировано 30 июня 2024 года.
  137. C++ parallel range algorithms
  138. Источник. Дата обращения: 23 марта 2024. Архивировано 23 марта 2024 года.
  139. Expose `std::atomic_ref` 's object address
  140. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  141. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  142. P2300R10: `std::execution`. Дата обращения: 1 июля 2024. Архивировано 1 июля 2024 года.
  143. Новости РГ21 — группы по стандартизации C++ | Антон Полухин, Яндекс — YouTube
  144. A Utility for Creating Execution Environments. Дата обращения: 25 ноября 2024. Архивировано 26 ноября 2024 года.
  145. P2079R8: Parallel scheduler
  146. Источник. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  147. P0543R3: Saturation arithmetic. Дата обращения: 14 ноября 2023. Архивировано 14 ноября 2023 года.
  148. 1 2 3 A free function linear algebra interface based on the BLAS. Дата обращения: 14 ноября 2023. Архивировано 5 ноября 2023 года.
  149. 1 2 Источник. Дата обращения: 13 сентября 2024. Архивировано 15 сентября 2024 года.
  150. 1 2 3 Philox counter-based RNG — NumPy v2.1 Manual. Дата обращения: 13 сентября 2024. Архивировано 13 сентября 2024 года.
  151. Источник. Дата обращения: 30 июня 2024. Архивировано 30 июня 2024 года.
  152. https://isocpp.org/files/papers/P1928R15.pdf
  153. P1494R4: Partial program correctness
  154. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3328r0.pdf
  155. Standard library hardening
  156. 1 2 C++26 — встреча ISO в Хагенберге / Хабр
  157. Standard library hardening should not use the 'observe' semantic
  158. Источник. Дата обращения: 8 августа 2023. Архивировано 10 августа 2023 года.
  159. Источник. Дата обращения: 31 июля 2023. Архивировано 21 июля 2023 года.
  160. C++ Concurrent Queues. Дата обращения: 31 июля 2023. Архивировано 31 июля 2023 года.
  161. Источник. Дата обращения: 31 июля 2023. Архивировано 24 сентября 2023 года.
  162. A Plan for C++23 Ranges. Дата обращения: 9 августа 2023. Архивировано 11 августа 2023 года.
  163. Trivial Relocatability For C++26
  164. 1 2 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/n5028.pdf