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

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

P.S.: некоторые ссылки на P???? документы могут ещё не работать, т.к. они не опубликованы. Со временем ссылки заработают