На днях в Праге прошла встреча международного комитета по стандартизации 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();
}
Всё из-за того, что в 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:
- Поддержка корутин в стандартной библиотеке
- Перевести стандартную библиотеку на модули
- Executors
- Networking
В первом пункте все будут ориентироваться на библиотеку CppCoro. Так что, если вы уже хотите использовать C++20 корутины, стоит начать с использования этой библиотеки.
С модулями, вторым пунктом, надо просто сесть и сделать, особых сложностей не предвидится.
А вот Executors — проблема. Их дизайн не очевиден, покрывает не все юз-кейсы, в текущем виде они никем не использовались, и дизайн всё ещё не утверждён.
Также комитет согласился приоритизировать предложения по направлениям:
- Reflection
- Pattern matching
- Contracts
Вместо итогов
C++20 готов, пора работать над C++23! Ближайшая встреча комитета будет летом, так что, если у вас есть достойные идеи для нового стандарта — делитесь ими на stdcpp.ru и в Telegram-чате ProCxx.
Ну а все желающие пообщаться с представителями комитета вживую — заглядывайте на митапы и C++ конференции*:
* Лайфхак: за билет на конференцию не надо платить, если ты докладчик.
Автор: antoshkka