Привет! На связи Антон Полухин из Техплатформы Городских сервисов Яндекса. Сегодня я расскажу о ноябрьской встрече Международного комитета по стандартизации языка программирования C++, в которой принимал активное участие. Это была первая из встреч, связанных с «полировкой» C++26. Другими словами, новые фичи C++ пока не появятся — комитет должен только проработать замечания всех стран-участников, включая наши замечания от России.

Однако от плана немного отступили и втащили некоторые новинки как ответы на пожелания участников комитета:

  • std::integer_sequence оброс новой функциональностью,

  • std::format научился в constexpr.

Помимо этого, поправили множество багов, перековыряли связку Hardening + Contracts, внесли улучшения во многие части стандартной библиотеки.


std::integer_sequence

В P1789 std::integer_sequence обзавёлся методами, позволяющими использовать его в structured binding и template for:

constexpr auto [...index] = std::make_index_sequence<COUNT>();
// Теперь с `index` можно работать как с обычным pack
auto sum = (index + ...);  // 0 + 1 + 2 + 3 + 4 +...

// Или даже вот так:
template for(constexpr size_t index : std::make_index_sequence<COUNT>()) {
    foo<index>(std::get<index>(some_tuple));
}

Новинка будет особенно полезна для рефлексии. Она позволит писать код компактнее, без лямбд для раскрытия std::integer_sequence:

constexpr auto members = nonstatic_data_members_of(
    ^^Aggregate,
    std::meta::access_context​::​​unchecked()
);

constexpr auto [...indexes] = std::make_index_sequence<members.size() / 2>();
serialize_first_half(aggregate.*[:members[indexes]:]...);

std::format

Большая радость (!) для всех пользователей: std::format научился работать в constexpr. Разве что с одним ограничением: нельзя форматировать с помощью локалей или чисел с плавающей точкой. Но даже с таким ограничением открывается большое окно возможностей: например, можно реализовать более продвинутые сообщения об ошибках в ваших библиотеках. Так, в ? userver FastPimpl вместо...

    // Use a template to make actual sizes visible in the compiler error message.
    template <std::size_t ActualSize /* ... */>
    static void Validate() noexcept {
        static_assert(
            Size >= ActualSize,
            "invalid Size: Size >= sizeof(T) failed"
        );
        // ...
    }

...можно будет по-человечески написать:

    // Use a template to make actual sizes visible in the compiler error message.
    template <std::size_t ActualSize /* ... */>
    static void Validate() noexcept {
        static_assert(
            Size >= ActualSize,
            std::format("Size should be set to at least {}.", ActualSize).c_str()
        );
        // ...
    }

Больше деталей в P3391.

Контракты и Hardening

Контракты C++ — одна из самых ожидаемых и при этом самых холиварных фич C++26. Поэтому подгруппа Evolution целых два дня работала над различными замечаниями от стран по контрактам.

Практически все замечания были отклонены на голосованиях. Одно из ярких исключений — hardening стандартной библиотеки через контракты.
История тут приключилась, на мой взгляд, занятная: некоторые страны хотели отвязать hardening стандартной библиотеки от механизма контрактов, некоторые (например, мы) хотели сохранить возможность кастомизировать поведение при срабатывании ассерт’а в стандартной библиотеке. А вот сами разработчики стандартных библиотек C++ заметили, что hardening с контрактами... не работает.

Засада крылась в формулировках:

  • Hardening — это про терминирование приложения в случае нарушения контракта стандартной библиотеки.

  • Контракты — это про возможность обнаруживать нарушения контракта и реагировать на них.

В итоге в стандарт закралось то, чего никто не хотел. А именно: «Стандартная библиотека считается hardened, даже если нарушение контракта просто логируется». При этом неопределённое поведение при использовании стандартной библиотеки оставалось: приложение продолжало работать, но при этом делать неожиданные вещи.

Как итог, на встрече единогласно приняли P3878: «Стандартная библиотека считается hardened, если приложение терминируется при нарушении контракта». Таким образом, мы закрыли сразу пять замечаний от стран-участников.

Trivial relocation

Trivial relocation в С++26 не будет. Было решено его удалить, так как практически все разработчики компиляторов сообщили, что есть платформы и ситуации, в которых текущее поведение trivial relocation невозможно реализовать.

Trivial relocation будет дорабатываться уже для C++29, а не в C++26 P3920.

Рефлексия и friend injection

Один из вопросов от участников из России был таким: «Если теперь рефлексия С++26 позволяет делать statefull metaprogramming, то не надо ли закрыть Core issue 2118, который пытается запретить statefull metaprogramming через friend injection?»

Вопрос важен в частности для пользователей библиотеки Boost.PFR, которая как раз может использовать хитрость из бага CWG2118 для рефлексии агрегатов в C++14.

Ответ Core: «Техника, описанная в CWG2118, позволяет намного больше, чем C++26 reflection. В частности, C++26 reflection injection не может вырваться за пределы класса или функции. При этом CWG2118 слишком строг в текущей формулировке, но закрывать как Not a Defect мы его не готовы».

Хорошо, что Boost.PFR работает и без использования хаков из CWG2118.

Прочие фиксы

  • optional<T&> теперь обязан быть trivially copyable P3836;

  • добавлены std::move и noexcept для различных flat_* контейнеров P3567;

  • std::execution::when_all теперь отправляет стоп-сигналы, только если один из «детей» их отправляет P388;

  • atomic_ref<T> научился конвертироваться в atomic_ref<const T> P3860.

И ещё почти сотня менее заметных багфиксов, доработок и улучшений.

Вместо итогов

Работа комитета не останавливается, подгруппы разбирают баги в онлайне. Сделать предстоит много: всего к C++26 было отправлено более 400 замечаний.

Остаётся нерешённым множество важных для нас комментариев, которые влияют на производительность и надёжность программ на C++. В частности, на этом заседании не дошли руки до P3725, который делает надёжный и безопасный std::ranges::filter, не подверженный проездам по памяти и Segmentation Fault в примерах наподобие:

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++, где можно будет поймать представителей РГ21, задать им вопросы, узнать что-то новое и интересное.

Буду рад встрече!

Комментарии (4)


  1. eao197
    26.11.2025 07:21

    Антон, а поднимается ли в комитете вопрос изменения периодичности выхода стандартов С++?
    Например, выпускать их не раз в три года, а раз в два года, раз в год или даже два раза в год?

    Сейчас получается следующая ситуация: есть некий жесткий дедлайн, ибо если что-то не входит в C++26, то затем придется ждать целых три года. Как раз пример с trivial relocation. В C++26 не попадает, но, возможно, уже в следующем году это предложение будет доработано, ему придется ждать до C++29.

    А вот если бы стандарт выходил раз в год, то окончательный trivial relocation мог бы войти в C++27.

    Как я понимаю, комитет сейчас работает по "train model" (если не ошибся с названием): в работе постоянно находится N предложений. К некоторой отсечке времени (за полгода-год до финализации) в новый стандарт включаются те предложения, прогресс по котором признан достаточным. А остальные предложения продолжаются разрабатываться.

    В этой модели, в теории, без разницы как часто делается отсечка по времени -- раз в три года или раз в два года. Или даже два раза в год.


  1. eao197
    26.11.2025 07:21

    И еще вот такой вопрос: а комитет хоть как-то озадачивается тем, что в реальной жизни есть два C++:

    • описанный в стандарте;

    • доступный обычным разработчикам в конкретных компиляторах.

    Причем одно далеко не всегда соответствует второму. И существует серьезный разрыв по времени между включением чего-то в стандарт и появлением этого же хотя бы в большой тройке компиляторов.

    Пока что есть ощущение, что комитету, мягко говоря, фиолетово. Мол, мы включим в стандарт некую фичу, а когда это доберется до простых смертных (особенно тех, кто вынужден пользоваться разными компиляторами на разных платформах) -- не наши проблемы.

    Я помню каково было ждать, пока нормальная поддержка С++98 в компиляторах появится. А потом поддержка C++11. После чего вот прям благодать настала со стандартами C++14 и C++17. Но вот уже с C++20 опять та же срань :(

    PS. Понимаю, что комитет не управляет разработкой компиляторов в Microsoft, Apple или RedHat. Но хотя бы о таком разрыве говорят?


    1. ZirakZigil
      26.11.2025 07:21

      Пока что есть ощущение, что комитету, мягко говоря, фиолетово

      А как иначе? Ну вот занимает столько-то времени у вендоров выкатить эту фичу, что комитету прикажете делать?


      1. eao197
        26.11.2025 07:21

        А как иначе?

        Мне думается, что если проблема есть, то:

        • начать можно с того, чтобы хотя бы признать ее официально;

        • после ее официального признания можно будет начать искать способы решения.

        Например, можно доработать "train model" по которой ведется развитие стандарта. Скажем, фича N получает статус "одобрена для включения в стандарт". После чего она не включается автоматически в стандарт, а ставится на паузу до тех пор, пока ее драфтовая реализация не появится, например, в gcc+clang+msvc. Когда драфтовая реализация появляется, фича включается в стандарт.

        При этом если при реализации фичи обнаруживаются подводные камни, то все это решается без оглядки того, что фича N уже отлита в граните включена в стандарт.