На днях в Праге прошла встреча международного комитета по стандартизации C++. И-и-и-и…



C++20 готов! Осталось поставить штампик от ISO, но это чисто формальный шаг, с которым не должно быть проблем.

Поздравляю всех с этим замечательным событием! Concepts, Coroutines, Modules, Ranges, std::format, constexpr new и constexpr алгоритмы+vector+string, datetime, jthread, span, bit_cast и многие другие мелкие и большие нововведения.

Что успели добавить и поправить в последний момент, что предложили разломать и что все хотят видеть в C++23 — обо всём этом под катом.

Приёмы из C в C++20


В последнее время сложилась традиция пугать начинающих разработчиков неопределённым поведением (UB) в C++. Пришло время это изменить!

Вот, например, такой код абсолютно валиден для C:

struct X { int a, b; };

X *make_x() {
  X *p = (X*)malloc(sizeof(struct X));
  p->a = 1;
  p->b = 2;
  return p;
}

Но в C++ с ним были большие проблемы. C оперирует байтами, а C++ работает с объектами. А у объекта есть время жизни, и до C++20 начало жизни у объекта считалось от вызова new.

Комитет серьёзно озаботился низкоуровневой работой с байтами и простыми структурами. Приняли улучшения, которые говорят, что определённый набор функций (memcpy, memmove, malloc, aligned_alloc, calloc, realloc, bit_cast) начинает время жизни объекта. Теперь большая часть низкоуровневых C-трюков гарантированно работает в C++.

bool стал надёжнее в C++20


Угадайте, в чём ошибка:

template <typename T, size_t N>
auto count_unique(const std::array<T, N>& v) {
    return std::unordered_set<T>{v.begin(), v.end()}.size();
}

Ответ
Если T является bool и в вашей имплементации стандартной библиотеки v.begin() и v.end() возвращают указатели (например, вы пользуетесь libc++ или libstdc++) — то функция count_unique будет возвращать 1.

Всё из-за того, что в std::unordered_set<bool>{v.begin(), v.end()} указатели будут неявно преобразованы к bool и вы получите выражение std::unordered_set<bool>{true, true}.

Преобразования в bool теперь считаются сужающими преобразованиями. Это позволило найти проблемы во многих кодовых базах (больше примеров в самом предложении P1957).

C++20 концепты стали быстрее


До недавнего времени вы могли написать концепт наподобие такого:

template <class T>
concept Reservable = requires(T v) {
    v.reserve(int{});
};

И удивляться тому, что он возвращает разные результаты:

struct Test;

static_assert(!Reservable<Test>);

struct Test {
    void reserve(int);
};

static_assert(Reservable<Test>);

В прошлый раз мы от страны отправляли комментарий: «Сделайте так, чтобы в концептах нельзя было использовать incomplete type, потому что иначе вы получаете множественные нарушения ODR». Наш комментарий отклонили, но мы частично получили нужный результат сейчас с предложением P2104.

Бонусом получаем более быструю компиляцию, так как компиляторы отныне вправе кэшировать результаты применения концептов к типам.

Мелкие правки в C++20


  • Ranges обзавелись методом ssize.
  • Internal linkage сущности более не видны при инстанцировании module linkage сущностей (компилятор на этапе компиляции вам подскажет, что не так).
  • Подкрутили правила для модулей, чтобы всяким тулзам было проще с ними работать.

А давайте всё сломаем в C++23 или C++26?


Длительные дебаты вызвало предложение об Application Binary Interface (ABI, не путайте с API). Были подняты интересные вопросы:

1. Мы можем полностью сменить ABI в C++23 и получить 5-10% прироста производительности.


При этом все старые C++ библиотеки придётся пересобрать, они не смогут линковаться с библиотеками с новым ABI. Вы не сможете воспользоваться в C++23 проекте библиотеками, собранными более ранними версиями C++.

Ну и разумеется, всегда найдётся старое коммерческое ПО, которое уже никто пересобирать не станет, но которое будет тащить свою стандартную библиотеку (да-да, видеоигрушки, я про вас!).

С небольшим перевесом голосов решили ABI в C++23 не ломать.

2. Давайте дадим пользователям гарантию, что мы будем стараться не ломать/менять ABI.


И тут решили гарантию не давать. Разные вендоры имеют различные планы на свои платформы, и порой они могут позволить себе сломать ABI, зачастую без вреда для пользователей.

А давайте добавим везде noexcept?


Исторически так сложилось, что в стандарте функции с предусловиями не помечаются как noexcept, даже если они никогда не кидают исключения. Вот, например, operator -> у std::optional:

constexpr const T* operator->() const;
constexpr T* operator->();
    Requires: *this contains a value.
    Returns: val.
    Throws: Nothing.

Он ничего не кидает, однако вместо noexcept словами написано что «Кидает: Ничего», потому что есть предусловие «*this содержит значение».

Пользователям c noexcept будет понятнее. Хорошая же идея в P1656!



Нет!

Есть целая подгруппа SG21: Contracts, которая придумывает общий механизм для проверки контрактов (пред- и постусловий). Обработчики контракта могут кидать исключение, если исключение кинуть из noexcept функции — будет std::terminate и приложение рухнет. Если же вставить особый костыль, что для контрактов исключения могут вылетать из noexcept функции… То всё равно всё ломается, type traits ориентируются на наличие noexcept, и начнут вам врать, помеченная noexcept функция с предусловием будет кидать исключение.

Но это не самая большая проблема. Есть форки стандартных библиотек, которые уже сейчас в ряде случаев явно вставляют проверки предусловий. Есть у вас, например, критически важный проект, доступность которого должна быть максимальной. Вы используете подобный форк, и если вдруг кто-то позвал std::vector::back() для пустого вектора — то вылетает исключение, которое обрабатывается выше по коду и начинает использоваться fallback. С правками из P1656 такая библиотека больше не может считаться стандартной.

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

Заслуги РГ21


Как и всегда, мы работали в различных подгруппах, делились опытом внедрения, презентовали предложения, авторы которых не смогли приехать.

Одна из выдающихся идей, которые нам посчастливилось представлять, — это идея Антона Жилина P2025 Guaranteed copy elision for named return objects. Её внедрение позволит создавать функции фабрики для объектов без copy и move конструкторов. Фактически это destructive move, который тайно существовал в стандарте с середины 90-х и был специально запрещён отдельными правилами языка.

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

Помимо этой идеи мы протащили идею P1990R0: Add operator[] to std::initializer_list через LEWG-I, получили полезный фидбэк на P1944R0: constexpr <cstring> and <cwchar>. Обе идеи Даниила Гончарова имеют все шансы оказаться в C++23.

На поприще std::hash нас ждал неожиданный провал. Обсуждение p1406r1: Add more std::hash specializations внезапно превратилось в обсуждение вырожденных граничных случаев и возможностей далёкого C++2*. В итоге комитет решил ничего не менять.

С SG6 и Numbers не срослось. Основные обсуждения SG6 пересеклись с обсуждениями ABI, из-за чего не набрался кворум в SG6. Из-за этого p1889: C++ Numerics Work In Progress, P2010: Remove iostream operators from P1889 и P1890: C++ Numerics Work In Progress Issues не обсуждались.

Планы на C++23


С начала разработки C++20 комитет стал действовать по плану. А именно — определять несколько крупных интересных идей для следующего стандарта, после чего на всех последующих заседаниях не рассматривать предложения по другим темам, если ещё не всё обсудили по основной.

Для C++23 такой план как раз утвердили в Праге. Основные приоритеты C++23:

  1. Поддержка корутин в стандартной библиотеке
  2. Перевести стандартную библиотеку на модули
  3. Executors
  4. Networking

В первом пункте все будут ориентироваться на библиотеку CppCoro. Так что, если вы уже хотите использовать C++20 корутины, стоит начать с использования этой библиотеки.

С модулями, вторым пунктом, надо просто сесть и сделать, особых сложностей не предвидится.

А вот Executors — проблема. Их дизайн не очевиден, покрывает не все юз-кейсы, в текущем виде они никем не использовались, и дизайн всё ещё не утверждён.

Также комитет согласился приоритизировать предложения по направлениям:

  • Reflection
  • Pattern matching
  • Contracts

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


C++20 готов, пора работать над C++23! Ближайшая встреча комитета будет летом, так что, если у вас есть достойные идеи для нового стандарта — делитесь ими на stdcpp.ru и в Telegram-чате ProCxx.

Ну а все желающие пообщаться с представителями комитета вживую — заглядывайте на митапы и C++ конференции*:


* Лайфхак: за билет на конференцию не надо платить, если ты докладчик.