
Привет! На связи Антон Полухин из Техплатформы Городских сервисов Яндекса. На днях в Кройдоне состоялась встреча международного комитета по стандартизации языка программирования 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++ с людьми, влияющими на стандарт языка.
