13 июня закончилась встреча комитета по стандартизации C++ (также известного как WG21) в Брно (Чехия), на которой комитет работал над будущим стандартом C++, C++29. В этой статье кратко пересказаны все принятые в него нововведения с примерами их использования и ссылками на оригинальные пропозалы для тех, кто захочет познакомиться с ними детально.
1. P3596 В стандарте теперь будет перечень всего неопределенного (и заодно IFNDR) поведения. Если раньше для того, чтобы найти все неопределенное поведение, приходилось грепать по всему документу слово «undefined», то теперь в стандарте будет целый раздел «Enumeration of Core Undefined Behavior» (если вы хотите изучить его уже сейчас — то его содержание приведено по ссылке выше на 60 странице). Приобщаться к прекрасному стало легче.
2. P3097 В C++26 приняли контракты (и их даже уже реализовали в gcc 16), но с многими ограничениями, одно из которых заключалось в том, что их нельзя было использовать в виртуальных функциях. Теперь можно:
struct X1 { virtual void f() pre(a) post(b); }; struct X2 { virtual void f() pre(c) post(d); }; struct Y : X1 { void f() override pre(e) post(f); }; struct Z : Y, X2 { void f() override pre(g) post(h); }; void t() { Z z; z.f(); // asserts g, h static_cast<Y*>(&z)->foo(); // asserts e, g, h, f X1& x1ref = z; X2& x2ref = z; x1ref.f(); // asserts a, g, h, b x2ref.f(); // asserts c, g, h, d x1ref.X1::f(); // asserts a, b void (X1::*pmf)() = &X1::f; (x1ref.*pmf)(); // asserts g, h }
3. P3668 Также в C++29 теперь можно не реализовывать постфиксный инкремент или декремент вручную, а реализовать только префиксный и определить постфиксный как =default, и компилятор сам его реализует.

4. P2287 Ранее в C++20 из C99 притащили «Designated initializers» (существует ли общепринятый перевод на русский для этого словосочетания?), но без возможности инициализировать с помощью них поля базовых классов (см. пример ниже; он также доступен на godbolt), хотя, казалось бы?
struct A { int a; }; struct B : A { int b; }; int main() { B b1 { .b = 10 }; // Работает в C++20 B b2 { .a = 10 }; // Не работает в C++20 B b3 { .a = 10, .b = 5}; // Не работает в C++20 }
В C++29 эту возможность решили добавить:
struct A { int a; }; struct B : A { int b; }; B{{1}, 2} // корректно в C++20 B{1, 2} // корректно в C++20 B{.a=1, .b=2} // теперь корректно в C++29 B{{.a=1}, .b=2} // теперь корректно в C++29 B{.a{1}, .b{2}} // теперь корректно в C++29 B{.b=2, .a=1} // по-прежнему некорректно
5. P3091 В некоторых случаях использовать operator[] у std::(unordered_)map дико неудобно, например код ниже не скомпилируется, если объект theMap объявлен как const:
const std::map<int, double> theMap = {...} double largest = -std::numeric_limits<double>::infinity(); for (int i = 1; i <= 100; ++i) largest = std::max(largest, theMap[i]);
Без const код скомпилируется, но это может привести к нежелательному поведению, связанному с тем, что operator[] вставляет сконструированные по-умолчанию объекты, если они отсутствуют в контейнере. Чтобы обойтись без этого, можно реализовать ту же самую логику с помощью find() (да и с помощью at() тоже, но я уверяю вас, вы не хотите этого делать):
double largest = -std::numeric_limits<double>::infinity(); for (int i = 1; i <= 100; ++i) { auto iter = theMap.find(i); if (iter != theMap.end()) largest = std::max(largest, iter->second); }
Но разве это красиво? Нет. И поэтому в C++29 у map, unordered_map и flat_mapтеперь есть метод lookup(), возвращающий std::optional:
constexpr double inf = std::numeric_limits<double>::infinity(); double largest = -inf; for (int i = 1; i <= 100; ++i) { largest = std::max(largest, theMap.lookup(i).value_or(-inf)); }
6. P3125 Вряд‑ли кому‑нибудь требуется это часто, но кому надо — тот пусть возрадуется. Теперь можно прятать данные (в том числе в constexpr контексте) в нижних битах (не в верхних, так как это было бы непереносимо) указателей совершенно стандартно без всяких UB.
template <typename T> class maybe_owning_ptr { enum class ownership { reference, owning, }; std::pointer_tag_pair<T *, ownership, 1> _ptr; public: constexpr maybe_owning_ptr(T* && pointer) noexcept: _ptr{pointer, ownership::owning} { } constexpr maybe_owning_ptr(T & ref) noexcept: _ptr{&ref, ownership::reference} { } constexpr decltype(auto) operator*() const noexcept { return *_ptr.pointer(); } constexpr T * operator->() const noexcept { return _ptr.pointer(); } constexpr ~maybe_owning_ptr() noexcept { if (_ptr.tag() == ownership::owning) { delete _ptr.pointer(); } } }; static_assert(sizeof(maybe_owning_ptr<int>) == sizeof(int *));
7. P3248 Стандарт определяет типы intptr_t и uinptr_t, но до сих самых пор не требовал от реализаций компиляторов их обязательное наличие — в стандарте эти типы помечены как опциональные. Теперь же их наличие считается обязательным. Сделано это по той причине, что эти типы очень полезны (да и реализованы практически во всех компиляторах практически на всех платформах) и многим авторам предложений в язык C++ хочется их использовать в API предлагаемых фичей (например, в P2835 и уже упомянутом P3125 эти типы хотели использовать), но их опциональность очень этому мешает. Теперь же они обязательны и использовать их можно с чистой совестью.
И это всё, что успел принять в стандарт комитет за встречу в Брно. Следующая будет в ноябре в Рио-де-Жанейро, и на ней комитет планирует продолжить работать над принятием различных фичей в C++29.
