
Привет! На связи Антон Полухин из Техплатформы Городских сервисов Яндекса. На днях в Кройдоне состоялась встреча международного комитета по стандартизации языка программирования C++, в которой я принимал активное участие. В этот раз (как и в прошлый), всё внимание было сосредоточено на C++26 и… теперь он готов! Осталось пройти формальные этапы в вышестоящих инстанциях ISO, и мы получим C++26 который заслужили. В нём будут:
reflection,
контракты,
SIMD,
линейная алгебра,
расширенные возможности сonstexpr,
hardening,
Hazard Pointer и RCU,
#embed,
executors,
и многие другие полезные вещи.
Дисклеймер: документы по некоторым из ссылок могут быть пока недоступны. Ссылки заработают в течение пары недель, как только будет полностью опубликована вся информация по встрече.
std::is_within_lifetime
На одной из встреч в C++ была добавлена функция для проверки, что объект p валидный и его можно использовать в compile‑time‑выражении:
template<class T> consteval bool is_within_lifetime(const T* p) noexcept;
Например, с помощью этой функции можно работать c union в compile‑time, спрашивая напрямую у компилятора, какой из элементов union сейчас активен. Пример реализованного на этом подходе std::optional есть в P2641R4.
Но автор решил не останавливаться на достигнутом! С P3450 функция научилась извлекать из компилятора информацию, можно ли делать downcast:
А почему столько внимания к is_within_lifetime?
Действительно, приблизительно то же самое можно получить с использованием dynamic_cast, если он не отключен через флаги компилятора.
Однако, std::is_within_lifetime() интересен тем, что открывает дверку к тому, чтобы C++ начал обзаводиться consteval функциями “вытаскивающими” тайные знания из компилятора. Например, позволяет “посмотреть” активный элемент union, что иначе невозможно реализовать без добавления дополнительных переменных (см. имплементацию std::variant).
Что если спрашивать у компилятора, и другие скрытые свойства? Сделана ли value initialization для переменной (занулена ли она?). Или запрашивать максимально используемый размер стека у функции? Наверняка можно придумать и другие подобные функции, пишите в комментариях, если у вас есть идеи…
std::atomic_ref::address()
Как я уже говорил в прошлых статьях о C++26, комитет настроился на большую безопасность и надёжность языка. Одна из таких новинок — изменение возвращаемого типа данных из T* std::atomic_ref<T>::address() на void* (P3936).
С T* проблема в следующем: люди совершают ошибки наличие множества разадресовываемых указателей в функции приводит к ошибкам. Особенно неприятно, когда такой код связан с многопоточностью — тогда ошибку крайне сложно найти. Например, если есть желание сравнить адреса переменных или посчитать хеши от адреса, то легко ошибиться и написать лишний * (вместо std::hash{}(ref.address()) написать std::hash{}(*ref.address()).
Ranges
std::views::filter обладает весьма неожиданными подводными камнями. Например:
// Воскрешаем монстров: auto dead = [] (const auto& m) { return m.isDead(); }; for (auto& m : monsters | std::views::filter(dead)) { m.bringBackToLive(); // undefined behavior }
А вот этот пример вообще не скомпилируется:
void constIterate(const auto& coll) { for (const auto& elem: coll) { //... } } // ... std::vector<std::string> coll{"Amsterdam", "Berlin", "Cologne", "LA"}; auto large = [](const auto& s) { return s.size() > 5; }; constIterate(coll | std::views::filter(large)); // compile‐time ERROR
И наконец, мучительная и сложно осознаваемая проблема:
std::vector<std::string> coll1{"Amsterdam", "Berlin", "Cologne", "LA"}; // Перемещаем длинные строки в обратном порядке в другой контейнер: auto large = [](const auto& s) { return s.size() > 5; }; auto sub = coll1 | std::views::filter(large) | std::views::reverse | std::views::as_rvalue | std::ranges::to<std::vector>();
Это только некоторые из проблем, на которые обратила внимание РГ от России и отправила комментарии в международный комитет C++ (и не мы одни!). И на встрече в Кройдоне проблема обрела частичное решение в P3725! Теперь общая рекомендация от комитета такова: по умолчанию перед фильтром всегда использовать std::views::as_input |. Такая конструкция уберёт лишнее кэширование из фильтра, заставит его быть гарантированно однопроходным и в целом работать без сюрпризов:
// Воскрешаем монстров, правильная и более быстрая версия: auto dead = [] (const auto& m) { return m.isDead(); }; for (auto& m : monsters | std::views::as_input | std::views::filter(dead)) { m.bringBackToLive(); // OK }
Изначальный план по решению проблемы включал в себя создание безопасного аналога std::views::filter. К несчастью, сейчас уже достаточно поздно вносить новый std::views в C++26, поэтому std::views::safe_filter появится только в C++29. Однако его легко реализовать самостоятельно:
namespace impl { struct safe_view_impl { template <class Filter> static constexpr auto operator()(Filter value) { return std::views::as_input | std::views::filter(std::move(value)); } }; } // namespace impl; inline constexpr impl::safe_view_impl safe_view{};
С правками из P3725 в нём заработает и constIterate. Есть и полный пример для экспериментов.
Ах да! Если вы гадали, что такое std::views::as_input, то это бывший std::views::to_input, который решили переименовать, чтобы было консистентно с std::views::as_rvalue в P3828.
Прочие замечания от разработчиков из России
В P4037 поправили переносимость кода для заголовочного файла <random>. Теперь unsigned char и signed char обязаны работать с uniform_int_distribution и другими классами из этого заголовочного файла.
Код uniform_int_distribution<std::uint8_t> стал переносимым на всех стандартах C++ (замечание приняли как исправление бага). Использование прочих неподдерживаемых типов перестало быть UB и теперь диагностируется во время компиляции.
На озвученные выше проблемы с <random> мы напоролись пару лет назад в Техплатформе Городских сервисов Яндекса — и нам не понравилось.
Другое наше замечание: разрешить обходиться без дополнительной косвенности при использовании std::function_ref. Например, во фреймворке ? userver мы активно используем function_ref, и нам бы не хотелось иметь дополнительную индирекцию при вызове function_ref, созданного от move_only_function или copyable_function. Оптимизацию разрешили в P3961.
И ещё несколько улучшений
std::simdполучил множество небольших улучшений в P3690, P3844, P3932, P4012.std::runtime_formatпереименовали вstd::dynamic_format, чтобы не было путаницы, ведь этот класс теперь можно использовать в compile‑time, а не только в runtime (P3953).Арифметические операции с насыщением были тоже переименованы, чтобы избежать непонимания, что же значит
sat_. В P4052 префикс перестал быть сокращённым и превратился вsaturation_.-
Множество улучшений приземлилось в executors.
В P4052 были заменены
_tнаtagдляsender_t,scheduler_t,operation_state_t,receiver_t, чтобы не путать теги с алиасами.В P3980 поженили
std::taskс аллокаторами executors.Документ P3826 переосмыслил механизм кастомизации sender алгоритмов.
parallel_scheduler улучшился в P3804: он избавился от виртуального деструктора и научился работать не только с inplace_stop_token (и это ещё не все улучшения!).
В P4159
std::spanлишился конструктора отstd::initializer_list. Зато в P3787R добавили возможность дляstd(::ranges)::uninitialized_fill*не указывать явно тип элемента, например:
void sample(std::span<MyStructure> range) { std::ranges::uninitilized_fill(range, {"some", "arg"}); // Раньше можно было было писать только вот так: std::ranges::uninitilized_fill(range, MyStructure{"some", "arg"}); }
В P3948 убрали лишнюю структуру
std::constant_arg_t, заменив её на использованиеstd::constant_wrapper. Другими словами, если вам нужна константа, представленная как тип, просто всегда используйтеstd::constant_wrapperилиstd::cw, вне зависимости от того, хотите ли вы держать число, строку или адрес функции.
Итоги
С++26 закончен — время заниматься C++29! И на него уже есть планы:
std::cstring_view/std::zstring_view,profiles,
units,
pattern matching.
Если у вас есть идеи или желание помочь с воплощением полезных идей в C++29 — пишите мне или делитесь идеями на сайте РГ21 С++. Кроме того, в скором времени состоятся интересные мероприятия, связанные с C++:
Zero Cost Conf (ожидайте анонсов!)
Приходите, будет интересно! Кроме того, можно будет вживую пообщаться о C++ с людьми, влияющими на стандарт языка.
Комментарии (46)

ReadOnlySadUser
03.04.2026 07:46И наконец, мучительная и сложно осознаваемая проблема:
А проблема-то в чём?)

antoshkka Автор
03.04.2026 07:46Будет проезд по памяти и Segmentation Fault. Можно попробовать воспользоваться ссылкой и самостоятельно раздебажить неочевидное поведение filter. Добавление
std::printlnв фильтр может помочь.
ReadOnlySadUser
03.04.2026 07:46От ответа теперь только больше вопросов:)
По какой памяти? Что значит "проезд"? Зачем мне вообще ranges, если они такое говно?)

ZirakZigil
03.04.2026 07:46Ну, всякие зипы, слайды удобнее чем руками. В примерах с ренжами при этом, почему-то, всегда что-то примитивное, что без ренжей и удобнее, и сложнее сломать:
auto nb = std::remove_if(coll1.begin(), coll1.end(), std::not_fn(large)); std::vector sub(std::move_iterator(nb), std::move_iterator(coll1.end()));Хотя тут, конечно, есть нюанс с мувабельностью и известностью размера

Arenoros
03.04.2026 07:46я вот уже 6 год наблюдаю за этими ренжами и вот ни как не пойму чего они так вперлись, и с каких ... эти конструкции из пайпов проще и удобнее чем банальный for известный всем и в котором не нужно гадать что где и куда перемещается, копируется и т.д.
По крайней мере во всех примерах которые показывают примеры этих самых ренжей, кроме экономии на 1% строчек в замен на доп. когднитивную нагрузку, я ни чего полезного не вижу. И это если в проект реально много тривиальных хождений по контейнерам.
sergio_nsk
03.04.2026 07:46Они для ленивых вычислений. Примеры на векторах - для краткости, они не показывают преимущества инетервалов.

Persik1
03.04.2026 07:46Ranges решают проблему композиции алгоритмов. Вместо того чтобы городить вложенные циклы или создавать временные контейнеры на каждом шаге, строишь пайплайн ленивых вычислений, а это плюс жизнь производительности потому что данные обрабатываются за один проход, но ценой читаемости

X-Ray_3D
03.04.2026 07:46Curves toCurves(const QPainterPath& pPath) { using QPP = QPainterPath; using El = QPP::Element; Curves curves; for(auto&& elements: v::iota(0, pPath.elementCount()) | v::transform(std::bind(&QPP::elementAt, pPath, _1)) // to Element | v::chunk_by(+[](const El&, const El& r) { return r.type; })) { // to Subpath Polygons // qInfo() << "elements" << elements.size(); // count of elements constexpr auto splitPaths = +[](const El&, const El& r) { return r.type > QPP::CurveToElement; }; auto subpaths = v::chunk_by(elements, splitPaths); // separate Curves Curve curve; for(auto&& [from, to]: v::pairwise(subpaths)) { // 'from' point to bezier Point in 'to' if if(curve.empty()) curve.emplace_back(static_cast<QPointF>(from.front())); if(to.front().type == QPP::CurveToElement) { // is arcTo // Проверка, является ли кривая Безье дугой окружности QPointF center; double radius; if(isArcOfCircle(from.back(), to[0], to[1], to[2], center, radius, 5e-3)) { // qInfo() << "Arc 1" << radius << center; curve.emplace_back(static_cast<QPointF>(to.back()), center, DIR(from.back(), center, to.back())); } else { // is lineTo drawCross45(center, Qt::red); // debug curve.emplace_back(static_cast<QPointF>(to.back())); } } else curve.emplace_back(static_cast<QPointF>(to.front())); } if(curve.size()) curves.emplace_back(std::move(curve)); } return curves; }

Persik1
03.04.2026 07:46Проще и безопаснее написать обычный for с if внутри. Да, не так модно и не в одну строчку, зато код будет читать даже джун, а не только три с половиной человека из комитета по стандартизации

SilverTrouse
03.04.2026 07:46Зачем городить еще одну конструкцию
std::views::safe_filter, если можно было бы просто изменить реализациюstd::views::filter?
antoshkka Автор
03.04.2026 07:46Это бы сломало пользовательский код в валидных местах использования, например:
auto sub = coll1 | std::views::filter(large) | std::views::reverse | std::ranges::to<std::vector>();
ZirakZigil
03.04.2026 07:46Как это вяжется с:
Теперь общая рекомендация от комитета такова: по умолчанию перед фильтром всегда использовать
std::views::as_input |. Такая конструкция уберёт лишнее кэширование из фильтра, заставит его быть гарантированно однопроходным и в целом работать без сюрпризовТ.е. рекомендуется по умолчанию использовать, но при этом оно в каких-то случаях что-то гарантированно сломает?

antoshkka Автор
03.04.2026 07:46Всё так. Рекомендуется по умолчанию использовать. Если вдруг код, с использованием аналогов
safe_filterне собирается, то надо задуматься над тем, что происходит и обратить на код внимание. Если всё ещё не понятно, почему не собирается - тоне стоит писать код, который вы не понимаетестоит код упростить. Если же вам всё понятно и нужен именно небезопасный фильтр - используйте его.
Mingun
03.04.2026 07:46Хм. Т.е. переименовать кучу вещей и поменять возвращаемые значения некоторых функций – это сборку не ломает, и ОК, а здесь возможно что-то сломается, и уже нельзя.
Комитет случайно не завел
std::committee_bool, где можно хранить его решения?
antoshkka Автор
03.04.2026 07:46Менялись имена вещей, которые повились только в C++26. Так как стандарт ещё не вышел, а все реализации помечают новинки как "экспериментальные" - то менять названия ОК.
Не ОК менять названия вещей, которые уже длительное время в стандарте (например добавились пару стандартов назад)

Skirikikaka
03.04.2026 07:46Чел из Яндекса не осилил привести пример из нововведения по линейной алгебре

antoshkka Автор
03.04.2026 07:46На этой встрече просто не было больших нововведений в линал. Примеры есть в прошлой статье, где линал непосредственно добавили https://habr.com/ru/companies/yandex/articles/801115/

Persik1
03.04.2026 07:46Комитет C++ собирается в Кройдоне, чтобы решить, как переименовать sat_ в saturation_. От этих решений зависит судьба высокопроизводительных вычислений во всем мире. Запомните этот коммент

monah_tuk
03.04.2026 07:46Поддержку корутин в std завезут? Работу с сетью?

antoshkka Автор
03.04.2026 07:46Сеть отложили в очередной раз, так что самое раннее в C++29 её увидим.
Корутины уже были добавлены в C++20, в C++23 приняли
std::generator. В C++26 появляется `std::execution::task`. Или вы больше ждали какие-то другие примитивы?
SilverTrouse
03.04.2026 07:46На счет сети, ее планируют делать на базе std::execution или будет включен в стандарт boost::asio?

antoshkka Автор
03.04.2026 07:46Boost.Asio уже работает с executors. Так что ответ "да" на оба вопроса

Belarus
03.04.2026 07:46Сеть разве не передумали добавлять в этот язык? Читал стать, што это невозможно, т.к. у сетей слишком разная реализацыя и не стандартизировать, а также обожглись на низкой производительности regex. Т.е. сторонние regex производительнее добавленной стандартной и это признано неудачей.

simplepersonru
03.04.2026 07:46а в рефлексию по итогу без изменений зашли пользовательские аттрибуты? Где-то раньше видел прототипы кода.

antoshkka Автор
03.04.2026 07:46Да, они в C++26 под именем "аннотации". Синтаксис у них похож на аттрибуты
[[=any_compile_time_object]]

Autochthon
03.04.2026 07:46Опережающее описание для вложенных классов в каком веке появится?
class Foo::Inner; // ошибка
antoshkka Автор
03.04.2026 07:46Как только вы реализуете прототип в компиляторе и напишите proposal. Дальше будет процесс обсуждения и по итогу, есть все шансы увидеть новинку в ближайшем стандарте

alabamaa
03.04.2026 07:46Есть предложение на C++29 добавить в стандартную библиотеку следующую функцию:
void EarnBillionDollars ( time_point deadline, long long bankaccount );
Undefined behavior не допускается.

haqreu
03.04.2026 07:46Какая короткая статья... А что там из линейной алгебры?

antoshkka Автор
03.04.2026 07:46В C++26 добавили функции для работы с векторами и матрицами (да-да! BLAS и немного LAPACK). Более того — новые функции работают с ExeсutionPolicy, так что можно заниматься многопоточными вычислениями функций линейной алгебры. Вся эта радость работает с std::mdspan и std::submdspan.
В целом формат статьи - "новости с последней встречи ISO". Если хочется полный обзор C++26, то надо прочитать соответствующие статьи за последние 3 года - получится весьма подробно по всем темам.

muhachev
03.04.2026 07:46превратили плюсы в полигон для гиков.

antoshkka Автор
03.04.2026 07:46А что именно вы бы хотели изменить в C++?

ReadOnlySadUser
03.04.2026 07:46Добавить сеть
Добавить приличные парсеры популярных текстовых форматов
Выкинуть всё говно, которое наворотили со строками и сделать по-человечески. Заколебало везде за собой таскать ICU.
Запретить устаревший синтаксис (хоть те же "эпохи" ввести)
Стандартизировать ABI
Хочется стандартный пакетный менеджер
Интрузивные контейнеры
Стандартизировать отключение исключений
Но в целом, С++ уже ничего не поможет) Он слишком многословный и хрупкий) Его придётся любить таким, какой он есть

Starl1ght
03.04.2026 07:46Изкоробочные сборщик, файл проектов и пекедж менеждер. В C# все это есть, например.

Fardeadok
03.04.2026 07:46Каждый раз радуюсь что ушел с этого языка. Все эти симпозиумы выглядят как поиски в болоте костылей для костылей чтобы поддерживать костыли

antoshkka Автор
03.04.2026 07:46Вы просто не читаете аналогичные отчёты по другим языкам программирования ;-) А соответственно не знаете о проблемных местах других языков программирования и о том, какие пути обхода проблем используются там

DEgITx
03.04.2026 07:46Я как-то в упор не понял, смотрел на примеры с фильтрами. Написано более менее понятно, однако: там неопределенное поведение, там ошибки по памяти, там куча других проблем... И вместо того чтобы исправить само поведение (а все намекает на это), мы добавляем новую конструкцию std::views::as_input. Это же так очевидно! И все будут знать и помнить, и главное понимать что она делает.

Kadzikage
03.04.2026 07:46И исправить ничего нельзя ведь это сломает будущую обратную совместимость , по этому пусть будет не внятное , а через три года сделаем фикс в виде нового safe-filter . Какая то бюрократия ради бюрократии

antoshkka Автор
03.04.2026 07:46будущую обратную совместимость
filterуже 6 лет с нами, аж с C++20А дальше тонкий вопрос - хотите ли сломать ~20% использований чтобы обнаружить 0.1%-1% UB? Комитет выбрал так не делать и идти по пути
safe_filter

klirichek
03.04.2026 07:46Про то, чтобы запрашивать у компилятора максимальный размер стека функции - да, компилятор, возможно, что-то ответит. Но, увы, этот ответ не будет универсальным.
Практический пример - вы узнали размер в compile time, ок.
А потом собранное приложение запускаете, внезапно, через qemu на другой архитектуре. Например, бинарь amd64 запускаете на arm64. И там, внезапно, размер стека оказывается другим; не тем, который подсказал компилятор. Особенно заметна разница при "холодном" запуске функции. Эмулятор что-то там делает - возможно, транспилирует код под актуальную архитектуру - и этот шаг кушает стек.
Quarc
03.04.2026 07:46Эм, так ведь то, что вы запускаете бинарь через Qemu на какой-то левой архитектуре вовсе не проблема и не зона ответственности компилятора, формировавшего бинарь. Это явно проблема и зона ответственности любознательного экспериментатора.
Kelbon
можно кратче
antoshkka Автор
Да, так ещё легче :) Можно ещё и `inline` добавить
Авторы libstdc++ не любят использовать лямбды в header файлах, это вызывает проблемы при линковке объектов собранных разными компиляторами. Так что в примере использовал максимоально надёжный вариант, вместо самого наглядного
Kelbon
знаю про лямбды и inline, но чтобы не пугать людей вот так сходу лучше написать кратко
И лямбду можно заменить на просто функцию, если не нужно требуемого стандарту эффекта "не искать по adl"