
Привет! На связи Антон Полухин из Техплатформы Городских сервисов Яндекса. Недавно в Брно состоялась встреча международного комитета по стандартизации языка программирования C++, в которой я принимал активное участие. В этот раз началась работа над C++29 и как раз о новинках и хочется рассказать:
UB и IFNDR
= default;std::intptr_tиstd::uintptr_tfloating point и
std::format/std::to_charsконтракты и виртуальные функции
lookupstd::pointer_tag_pairstd::cwи строковые литералыname_hintиstack_size_hint
UB и IFNDR
Мало кто из разработчиков на C++ не слышал про злой и страшный Undefined Behavior (оно же UB, УБ, неопределённое поведение). У UB есть такой же злой брат близнец “ill formed no diagnostic required” (IFNDR). Как правило IFNDR используется когда проверку на неправильное использование C++ можно сделать уже на этапе линковки и при этом проблему очень долго/сложно диагностировать. Например, если вы напишете в 1.cpp файле функциюvoid foo(int a = 1, int b = 2), а в 2.cpp файле ту же функцию, но с другими параметрами по умолчанию void foo(int a = 2, int b = 1), то натолкнётесь на IFNDR.
Так вот, искать UB и IFNDR на страницах стандарта до C++29 было весьма неприятным занятием: мало того, что данные ужасы разбросаны по разным главам, так ещё и зачастую непонятно что надо сделать, чтобы на такую проблему натолкнуться.
В C++29 решено было собрать все явно описанные UB и IFNDR в ядре языка, и поместить их в отдельные секции стандарта. При этом сделать для них понятное описание и примеры:

Новые секции специально предназначены для того, чтобы их могли читать нормальные люди, а не эксперты по языку описания стандарта языка C++. Так что прошу, приобщайтесь к UB и IFNDR.
Разумеется C++ комитет не останавливается. Если пройтись по указанным выше спискам неопределённого поведения, то можно заметить несколько недочётов или пунктов из древнейших времён, которые на современных компиляторах можно спокойно проверять на этапе компиляции. Так например в C++26 вы могли написать свой оператор delete который кидает исключения и помечен как noexcept(false):
struct X { void operator delete(void*) noexcept(false) { throw "oops"; } };
Однако, попытка воспользоваться им была описана как UB:
void f() { X* x = new X(); delete x; // undefined behavior }
В C++29 решено было убрать это неопределённое поведение, на этапе компиляции проверять и запрещать писать noexcept(false) на operator delete. Помимо этого исправления из P3424, зафиксировали поведение floating point типов в constexpr (P3899). Теперь если в процессе вычисления на этапе компилирования получится +/-Inf или +/-NaN то будет ошибка компиляции, что положительно влияет на надёжность вычислений. А ещё в P2243 убрали implementation defined поведение про объявлении шаблонных функций с extern "C", запретив такое объявление.
= default
Если вы когда-то писали свои итераторы, то наверняка сталкивались с необходимостью писать префиксные и постфиксные операторы ++:
class Iterator { public: constexpr Iterator& operator++() { ++member_; return *this; } constexpr Iterator operator++(int) { auto copy{*this}; this->operator++(); return copy; } private: int member_{0}; };
Если приглядеться к любой кодовой базе повнимательнее, то можно заметить что постфиксный operator++(int) в подавляющем большинстве случаев выглядит как “скопировать текущий итератор из *this, сделать префиксный инкремент для *this, вернуть копию”.
Неприятный boilerplate. Поэтому решили позволить писать = default на постфиксных операторах ++ и -- в P3668:
class Iterator { public: constexpr Iterator& operator++() { ++member_; return *this; } constexpr Iterator operator++(int) = default; private: int member_{0}; };
А в P3785 применили новый функционал ко всему описанию библиотеки в стандарте.
std::intptr_t и std::uintptr_t
Помимо неопределённого поведения и boilerplate в C++ есть ещё и проблемы с переносимостью кода. Так например intptr_t и uintptr_t в C++ приехали из C, и там они помечены как “опциональные”. А значит, что на платформе может их и не быть, и надо аккуратно обкладывать свой код макросами, проверять наличие этих типов и думать о замене для платформ где эти типы отсутствуют.
Вот только исследование в P3248 показало, что данные типы есть практически на всех платформах (за исключением 2-3 достаточно редких) и более того, стандартные библиотеки C++ используют эти типы без всяких проверок макросов.
В итоге, решили не усложнять жизнь пользователям, и сказать что с C++29 std::intptr_t и std::uintptr_t всегда доступны.
floating point и std::format/std::to_chars
Смотрите какая оказия:
auto s1 = std::format("{}", 120000.0); // "120000" auto s2 = std::format("{}", 100000.0); // "1e+05"
Вроде бы два одинаковых по длине floating point числа, однако форматируются они по разному. Такое поведение связано с тем, что под капотом используется std::to_chars, который по умолчанию старается сделать максимально короткую строчку с записью числа. Вот только подобный подход мало того что вызывает недоумение у пользователей языка, так ещё и не совпадает с поведением в других языках программирования (например с Python, под впечатлением от которого и делалась библиотека fmt, послужившая прототипом для std::format). А ещё, такой подход кушает лишнее CPU. Если не вычислять длину числа в текстовом представлении а просто задать диапазон значений для вывода числа в экспоненциальной форме, то производительность подрастает на 15%:
----------------------------------------------------- Benchmark Time CPU Iterations ----------------------------------------------------- normal 77.5 ns 77.5 ns 9040424 garbage 91.4 ns 91.4 ns 7675186
Теперь, с P3505, std::to_chars выводит числа более предсказуемо и приятно:
auto s1 = std::format("{}", 120000.0); // "120000" auto s2 = std::format("{}", 100000.0); // "100000" auto s3 = std::format("{:L}", 120000.0); // "120.000" auto s4 = std::format("{:L}", 100000.0); // "100.000"
Контракты и виртуальные функции
В C++26 приняли контракты - возможность проверять пред/постусловия на функциях. Более того, компилятор “видит” эти контракты и может убирать лишние проверки и даже на этапе компиляции предупреждать о проблеме. Напомню, как контракты выглядят и работают на неких псевдокодных условиях a, b, e и f:
struct X1 { void f() pre(a) post(b) {} }; struct Y : X1 { void f() pre(e) post(f) {} }; void t() { X1 x; Y y; x.f(); // проверяет a, b y.f(); // проверяет e, f }
Вот только в C++26 запретили использовать контракты на виртуальных функциях. Ну а в C++29 в P3097 - разрешили. При этом поведение следующее:
сначала проверяется предусловие от статически известного типа
потом происходит virtual dispatch и проверяются предусловия для выбранной функции
по завершении функции выполняется постусловие
виртуальный диспатч завершается и вызываются постусловия для статического типа
Пример:
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(); // проверяет g, h static_cast<Y*>(&z)->f(); // проверяет e, g, h, f X1& x1ref = z; X2& x2ref = z; x1ref.f(); // проверяет a, g, h, b x2ref.f(); // проверяет c, g, h, d x1ref.X1::f(); // проверяет a, b void (X1::*pmf)() = &X1::f; (x1ref.*pmf)(); // проверяет g, h }
Последние две строчки особенно занятны: member function pointer не несёт в себе информации о контрактах с C++26, а значит “статический” контракт проверяться не будет.
lookup
Если вы разрабатываете на C++, то раз в недельку да пишете что-то наподобие:
auto do_something(int key, const std::unordered_map<int, int>& cache) { int result = 42; if (auto it = cache.find(key); it != cache.end()) { result = it->second; } return result; }
Казалось бы, простая задача “если нет значения по ключу то верни дефолт”, но вот решается она в неприличное количество строк кода, да при том с итераторами.
В P3091 решили покончить с этим безобразием и добавили во все ассоциативные контейнеры методы std::optional<Value&> lookup(const Key&). С ними код становится приятным для чтения и написания:
auto do_something(int key, const std::unordered_map<int, int>& cache) { return cache.lookup(key).value_or(42); }
std::optional обладает монадическими методами и удовлетворяет концепту диапазона, так что можно писать и более хитроумные конструкции:
auto do_something(int key, const std::unordered_map<int, int>& cache) { return cache.lookup(key) .or_else([&cache] { return cache.lookup(kFallbackValue); }) | std::views::transform(...) ; }
std::pointer_tag_pair
P3125 привнёс в стандарт С++29 возможность тегировать указатели, в том числе и на этапе компиляции:
template <typename T> class maybe_owning_ptr { enum class ownership: unsigned { reference, owning, }; std::pointer_tag_pair<T *, 1, ownership> 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 *));
Учтите что std::pointer_tag_pair использует только наименее значащие биты для хранения тегов. Использование старших битов считается непереносимым, и решено было пока их не трогать.
std::cw и строковые литералы
В C++26 есть универсальный тип “константа времени компиляции” - std::constant_wrapper. Подобные типы в C++ не редкость, так уже давно имелся std::integral_constant и std::true_type/std::false_type. Так вот, в последний момент в std::constant_wrapper добавили правку, позволяющую использоваться std::cw со строковыми литералами:
auto a = std::cw<"foo">; auto b = std::cw<"bar">;
Вот только с этой правкой код
template <int I> void f(std::constant_wrapper<I>) { // ... } int main() { f(std::cw<5>); }
перестал собираться:
<source>:9:6: error: no matching function for call to 'f(conststd::constant_wrapper<std::_CwFixedValue<int>{5}, int>&)' candidate 1: 'template<int I> void f(std::constant_wrapper<((std::_CwFixedValue<int>)I)>)' template argument deduction/substitution failed: mismatched types 'int' and 'const std::_CwFixedValue<int>'
Более того, ничего полезного со строковыми литералами в std::constant_wrapper тоже сделать нельзя:
auto a = std::cw<"foo">; auto b = std::cw<"bar">; a == b; // ошибка компиляции
В связи с этим в P4206 правку откатили (в том числе откатили и для C++26). Удобной работой со строками во время компиляции продолжат заниматься в P0424, P3380 и P3554.
name_hint и stack_size_hint
P0424 позволил задавать имя для потока ОС и выставлять размер стека потока:
#include <thread> void f(int); int main() { std::thread thread( // для std::jthread тоже работает: std::thread::name_hint("fs-worker"), std::thread::stack_size_hint(512*1024), f, 42); // ... thread.join(); }
Теперь если вы через утилиту top выведете имена потоков для написанного выше приложения, то увидите “fs-worker”. А если приложение упадёт, то в core file тоже будет указано имя потока, что ощутимо упрощает диагностику проблем (мы в Техплатформе Городских сервисов Яндекса давно пользуемся подобным механизмом и в ? userver все потоки имеют понятные имена).
Прочие мелочи
P0424 позволил использовать индексирование для пака шаблонных шаблонов:
template < template <typename> typename... TT> struct S { template <typename T> using First = TT...[0]<T>; };
P3428 добавил возможностей для батчевой работы с hazard pointer.
Как всегда приземлились новые улучшения в ranges в P3052, в simd в P3319 и в P3772, в mdspan в P3242.
Ну и наконец в P3104 приземлились новые операции для работы с битами:
bit_reverse(uint32_t{0x00001234}) // 0x24c80000 bit_repeat(uint32_t{0xc}, 4) // 0xcccccccc uint32_t x = /* ... */; // a b c d uint32_t m = 0b0101; // 0 1 0 1 uint32_t z = bit_compress(x, m); // 0 0 b d uint32_t x = /* ... */; // a b c d uint32_t m = 0b0101; // 0 1 0 1 uint32_t z = bit_expand(x, m); // 0 c 0 d
Итоги
До выхода C++29 ещё 3 года, работа вовсю кипит! Если у вас есть хорошие идеи, как сделать C++ лучше - то смело несите их в stdcpp.ru. А ещё лучше - беритесь за интересные идеи, пишите proposal и мы поможем вам донести идеи до международного комитета по стандартизации C++.
P.S.: Если кто был на Back2Back конференции, то эта статья может показаться вам знакомой. Всё потому, что мы рассказываем о новостях C++, Boost и userver не только на Хабре, но и практически на всех C++ конференциях. Ну а если вы вдруг никогда не ходили на конференции - стоит хотя бы разочек попробовать, вдруг понравится :)
Комментарии (62)

Janycz
17.08.2026 07:49Теперь если в процессе вычисления на этапе компилирования получится
+/-Infили+/-NaNто будет ошибка компиляции, что положительно влияет на надёжность вычислений.А если нужен NaN с нагрузкой?

antoshkka Автор
17.08.2026 07:49Для получения
NanиInfв constexpr можно пользоваться `std::numeric_limits<T>::quiet_NaN()` и `std::numeric_limits<T>::infinity()`. Умножать эти значения на-1или просто использовать унарный оператор `-` всё ещё можноА каким образом сейчас вы задаёте payload для NaN?

Janycz
17.08.2026 07:49Каноничный способ это std::nan из <cmath>, но он не constexpr. UB-реализации (которые тем не менее работают) через union и reinterpret_cast тоже не constexpr. Использование memcpy тоже не constexpr. В g++ для этого есть __builtin_nan, который, по сути, constexpr (но не всегда). Надо признать, что сейчас адекватного способа для этого нет. Более того, __builtin_nan и std::nan принимают на вход строку, а std::to_string (и другие функции преобразования) не constexpr.
Но, предлагается унифицированное поведение, которое ухудшает текущее поведение. Из прочитанного предложения по ссылке предлагается сделать любое выражение constexpr double d = expression невалидным, если expression возвращает неопределенность. Это также относиться также к std::numeric_limits<T>::quiet_NaN() и подобным, что они constexpr сейчас, но предлагается сделать не constexpr.
antoshkka Автор
17.08.2026 07:49Каноничный способ это std::nan
Если готовы побороться за payload в nan, то с радостью помогу вам. Но вам придётся проработать основные моменты и написать proposal. Подозреваю что там будут трудности, кажется некоторые платформы не позовляют делать
-nan, а что у них с payload для nan не представляю.Но, предлагается унифицированное поведение, которое ухудшает текущее поведение
На самом деле всё хорошо, стандартизировали текущее поведение GCC. Там даже есть достаточно понятные примеры:
constexpr std::float32_t min = std::numeric_limits<std::float32_t>::min(); // OK constexpr std::float32_t max = std::numeric_limits<std::float32_t>::max(); // OK constexpr std::float32_t inf = std::numeric_limits<std::float32_t>::infinity(); // OK constexpr std::float32_t nan = std::numeric_limits<std::float32_t>::quiet_NaN(); // OK constexpr std::float32_t inf2 = inf * 2; // OK, also positive infinity constexpr std::float32_t zero = min / max; // OK, result cannot be represented, and is rounded to zero constexpr std::float32_t oflo = max * 2; // error: non-finite result but operands are finite ([expr.const.core]) constexpr std::float32_t nan2 = nan * 2; // OK, propagating a NaN constexpr std::float32_t udef = inf * 0; // error: result is NaN but neither operand is NaN ([expr.const.core]) constexpr std::float32_t div0 = max / 0; // error: division by zero is undefined ([expr.mul], [expr.const.core])
То есть всё работает ожидаемо, NaN и Inf можно получать в compile time из numeric_limits, но нельзя "случайно" их создать выражением без NaN/Inf
Janycz
17.08.2026 07:49Странно, вот они привели пример, указанный вами как пример того, что должно быть, но при этом пишут в п. 3.6:
The current wording in [expr.pre] paragraph 4 is clear that any expression that produces NaN has undefined behavior. NaN is neither mathematically defined nor is it defined to be in the range of representable values (even intuitively, it would have to be outside any range).
However, the standard library doesn't seem to care about this, considering that
numeric_limits<T>::quiet_NaNandnumeric_limits<T>::signaling_NaNhave been markedconstexpr.И как такое понимать? Отсюда непонятно, что они, согласно 3.6, считают верным поведением. И только вот пример это поясняет.
Если готовы побороться за payload в nan, то с радостью помогу вам. Но вам придётся проработать основные моменты и написать proposal. Подозреваю что там будут трудности, кажется некоторые платформы не позовляют делать
-nan, а что у них с payload для nan не представляю.Есть std::numeric_limits<T>::is_iec559 -- если он истинный, то nan будет. Я вот также удивлен, что std::to_string не constexpr, хотя сейчас конструкторы в std::string и operator+ для std::string являются constexpr.

antoshkka Автор
17.08.2026 07:49Вы правы, IEEE 754 обязывает payload передаваться "An operation that propagates a NaN operand to its result and has a single NaN as an input should produce a NaN with the payload of the input NaN if representable in the destination format."
Он же позволяет игнорировать знак при Nan в подавляющем большинстве операций "For all other operations, this standard does not specify the sign bit of a NaN result".Ну и стандарт C++ говорит что `is_iec559 == true` значит что для типа выполняется IEEE 754.
Так что больших проблем при защите proposal быть не должно

SilverTrouse
17.08.2026 07:49Развитие рефлексии планируется? Все еще хотелось бы видеть token injection , к примеру на базе https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3294r2.html

domix32
17.08.2026 07:49auto s3 = std::format("{:L}", 120000.0); // "120.000"что-то не очень понял почему такой вывод. Оно десятичный разделитель по-умолчанию в точку превратило?
std::pointer_tag_pair
прочитал мотивацию, но так и не понял какой у них юзкейс. HAMT в std вроде так и не появилось ни в каком виде, альтернативных аллокаторов вроде тоже. Заготовка для hazard pointers? Кстати слышно ли что-то про стандартные каналы ?

antoshkka Автор
17.08.2026 07:49почему такой вывод
`"{:L}"` выводит с использованием текущей локали. В примере стоит локаль с разделителем `.` между тысячными. Обычный `"{}"` выводит без использования локалей, и будет просто "120000"
какой у них юзкейс
Тут как вам подскажет фантазия. Весьма полезная штука при создании своих деревьев, списков и прочих контейнеров

Daddy_Cool
17.08.2026 07:49Джентльмены, а есть ли какая-нибудь книжка которая рассказывает зачем нужны разные фичи С++?
Т.е. вот я знаю Си, и умею программировать в стиле 70-х, ну там - загрузить данные, посчитать, выгрузить. Могу что-то на BC++Builder "нарисовать", но вот дальше... Когда умеешь что-то низкоуровнево (да, я еще слегка умею на асме) возникает вопрос "а нафига нам это всё в зоопарке", это некая ментальная ловушка, зачем учиться управлять экскаватором если можно лопатой выкопать яму (и возможно получится быстрее, если задача разовая).
Хочется примеров - типа вот вам надо это (а надо потому-то и потому-то) и вот как это легко и изящно решается средствами языка.
domix32
17.08.2026 07:49У пропозалов обычно есть секция про мотивацию, правда не сказать что она везде достаточно подробная чтобы полноценно объяснить её.

naky
17.08.2026 07:49Джентльмены, а есть ли какая-нибудь книжка которая рассказывает зачем нужны разные фичи С++?
В принципе любая. Например, Beginning c++26, Ivor Horton, Peter Van Weert.
За простыми примерами использования фич можно заглянуть в примеры на https://en.cppreference.com/

Jijiki
17.08.2026 07:49если знаете С вы не ограничены, ставьте при компиляции 23/26 стандарт, используйте структуры, деструкторы, классы, лямбды, помните в С управление памятью например в текстовом редакторе? так вот это можно сварганить на С++, или взяв std::vector, или написав свою обертку дженерика по принципу того управления памятью со всеми вытекающими, по сути С++ в базе даёт просто удобство использования стандартных контейнеров и возможность создания своих контейнеров, вопрос для досуга(напишите функцию на С++, которая понимает где const char[] а где const char*), вы задаёте вопрос по-сути правильный тут главное точно понять зачем мы изобретаем всё заново и для чего.... в С++ всё для этого есть, разве что ждём Send/Receive или качаем либу от Nvidia или еще кого )
отвечая на вопрос, С++ имеет возможность уйти от никоуровневости за счет абстракций/библиотек, или можно написать проект снизу вверх, замиксив асм/симд/С/фишки в С++, которые есть в стандартной библиотеке, например всё теже маллок и тп, так же есть возможность заюзать другие аллокаторы mimalloc jemalloc..., это как фишки зиг, посмотрите язык на досуге, какие там фишки(компил тайм, аллокаторы, симд из коробки удобный, синтаксис визуально сишный, свой билдер, так же можно билдить С/С++ проекты. посмотрите может вам этого достаточно будет)
получается у С++ фишки теже, лямбды - анонимные классы, классы/структуры деструкторы фундамент по-сути наверно, есть констекспр тоже, можно написать свои итераторы, есть концепты, конструкторы/операторы перемещения/копирования, возможность обобщенного кода, этого поидее за глаза хватает...
корень вашего вопроса, лежит глубоко на стыке того, как работает сам язык, проще просто следовать его правилам компилятор же пишут другие люди, так или иначе всех фишек, которые может тот или иной компилятор врятли пользователь узнает....
а в эпоху где всё перепроверять и zero trust - это практически нереальная миссия....

Siemargl
17.08.2026 07:49Ну например Мейерса. Как раз как апгрейд курс до С++14. (С++17 не сильно улучшен)
Староватая краткая аннотация.
Ну а про С++20 и новее еще не смотрел и сам, там очередной виток ада и содомии =)

Daddy_Cool
17.08.2026 07:49Я посмотрел, но честно говоря ничего не понял. Хочется именно жизненных, а не программистких примеров. )
eao197 std::map или std::set? Нет, я не знаю, что это. Точнее, теперь знаю - попросил чтоб Deepseek объяснил.
HiItsYuri
17.08.2026 07:49std::map или std::set? Нет, я не знаю, что это.
Тогда кажется что вам или рано, или не нужно это знать. То есть для понимания достаточно начать писать на языке что-то сложнее hello world, банально взять любую задачу с литкода.
Хотя как писать около прод код без базовых структур данных для меня загадка.

eao197
17.08.2026 07:49Нет, я не знаю, что это.
Интересно что же вы такое программируете, что вам не нужны понятия “множество” и “словарь”.

Gay_Lussak
17.08.2026 07:49Да эмбедд скорее всего. На слабых архитектурах динамическую аллокацию не используют. Поэтому почти все контейнеры из std сразу отсекаются.

eao197
17.08.2026 07:49Возможно. Но давайте послушаем первоисточник.
Вангую, что человек работает в узкой специфической области, в которой ему вполне хватает Си и фичи C++ не нужны чуть меньше, чем полностью.

Daddy_Cool
17.08.2026 07:49@Gay_Lussak
@eao197
Первоисточник вещает. )
Пример 1. Есть расчетная область, трехмерная, хотя пофиг на мерность - неструктурированная сетка, это массивы - координаты x,y,z. Есть граница, на ней что-то происходит, задано какое-нибудь хитрое граничное условие. Внутри области надо решать уравнение Лапласа, потом двигать сетку в соответствии с каким-нибудь алгоритмом. Здесь можно подцепить видеокарту и часть расчетов делать на CUDA.
Пример 2. Нужно генерить командные файлы для АЦП. На BC++ нарисован интерфейс, там указываются каналы АЦП, в какие файлы писать, как именовать, и т.п... Дальше работа со строками, чтобы сгенерить нужный текстовый файл который скормится консольной программе управляющей АЦП.
Пример 3. Управление древним координатным устройством работающим от LPT-порта и принимающем данные с АЦП. Интерфейс на BC++, и дальше подать байты в порт, принять байты из порта.
Пример 4. Загружаем в программу раскадровку видео, берутся координаты и интенсивность некоторых точек, дальше это считается по каким-то формулам и выводится.
---------
Самое сложное что мне приходилось использовать из структур - структуру с массивом указателей на функции, задаем параметрически много трёхмерных линий, каждая пространственная линия это проводник с током, дальше считаем интегралы - магнитное поле от всего.@HiItsYuri
Это да, решил посмотреть задачи про контейнеры, нашел задачу про контейнер с водой ))) Да... чуток другой контейнер.
https://leetcode.com/problems/container-with-most-water/description/
Навскидку - всё архипросто, нужен двойной цикл и всё. Сложность N^2 от количества элементов. О! Первая подсказка, говорит, что это неэффективно. Наверняка если чуток подумать можно оптимизировать и брать не все элементы. А! Это знаменитая задача и интересно именно решить с линейной сложностью. ;)------
Вообще это может быть отдельным разделом юмора - то как программируют научные сотрудники.
eao197
17.08.2026 07:49Спасибо.
Грубо говоря, по большей части это то, что в моем окружении называлось “вычислительным кодом”. И где самое сложное – это выбор алгоритмов и параметров расчетов, а не структур данных.

Daddy_Cool
17.08.2026 07:49Ну да, причем больше всего я горжусь программой с менюшками и окошками на BC++ )))))))).

eao197
17.08.2026 07:49Тогда вам особо и не нужно париться. Когда основная проблема в предметной области, математике и алгоритмах, тонкости ЯП особо и не нужны.

asher111
17.08.2026 07:49Вообще это может быть отдельным разделом юмора - то как программируют научные сотрудники.
Это как-бы давно известно - научные сотрудники на любом языке программируют как на фортране

Daddy_Cool
17.08.2026 07:49Кстати да, я иногда подумываю с Си перейти на Фортран.
https://habr.com/ru/articles/1026398/
Siemargl
17.08.2026 07:49Я бы предложил воспользоваться плодами прогресса, и посмотреть новые, заточенные на вычисления языки.
Я немного лет назад переводил статью - как раз можно глянуть как это выглядит для математики.
С другой стороны, переопределение операторов в C++/D, и возможность замены стандартного аллокатора C++ многого стоят.

eptr
17.08.2026 07:49зачем нужны разные фичи С++?
Основные ощущения от C++ после C — наличие автоматизмов.
Определяется переменная какого-то (моего) класса — вызывается (мой) конструктор.
Заканчивается у этой переменной время жизни — вызывается (мой) деструктор, причём причина, по которой оно заканчивается, не важна, деструктор будет вызван все равно.
Происходит копирование этой переменной — вызывается конструктор копии.
Могу запрограммировать, что должно происходить с объектом (моего) класса, как хочу.Можно переопределить операции типа +, - и так далее.
Складываются две такие переменные — вызывается (мой)operator +.
Могу поэтому запрограммировать сложение объектов (моего) класса, как хочу.Есть исключения — что-то типа
longjmp, только значительно лучше: в частности, отрабатывают деструкторы переменных, время жизни которых заканчивается при раскрутке стека.Вот, что такое автоматизмы.
Если они не (сильно) нужны, лучше не лезть в C++.

Daddy_Cool
17.08.2026 07:49Видимо не нужно ).Я иногда порываюсь придумать какой-нибудь класс, но... не могу понять чем мне это улучшит жизнь. Видимо я просто не пишу достаточно сложных программ.

Belarus
17.08.2026 07:49зачем нужны разные фичи С++?
Вот наглядно некоторые: https://hackingcpp.com/news/2022-11-20

ImagineTables
17.08.2026 07:49Заменить
constнаmut? Нет.
Заставить при наличии[[nodiscard]]обрабатывать КАЖДЫЙstd::unexpected? Нет.
Дать изconstexprбезопасный доступ к памяти и диску? Нет.
Запретитьnoexcept(false)для деструкторов? ДА!!!P.S.
std::format, как минимум под VC, тащит за собой все кишки локалей дат, и одна строкаstd::format(L"{}", 5)раздувает бинарь на сотни килобайт в релизе. В то время как прототип (ц) как-то умеет собираться модульно и размер увеличивается на столько, на сколько ты его юзаешь. Я бы начал улучшатьstd::formatс этого. А продолжил бы поддержкой именованных аргументов в компайл-тайме. Вот чего действительно не хватает. А кто пишет пустые брэкеты{}, а потом удивляется непредсказуемому результату, тот сам себе злобный буратино.
antoshkka Автор
17.08.2026 07:49std::format, как минимум под VC, тащит за собой все кишки локалей дат, и одна строкаstd::format(L"{}", 5)раздувает бинарь на сотни килобайт в релизеДа, такая же история и с libstdc++ от GCC. Реализации стандартной библиотеки пока помещают всю имплементацию в заголовочный файл, чтобы не думать о ABI и его сломе. Когда реализации устаканятся и дооптимизируются, то они будут внесены в cpp. В новом libstdc++ от GCC даже есть уже нужный макрос сборки, но по умолчанию пока всё в заголовочных файлах

ImagineTables
17.08.2026 07:49А почему тогда fmtlib — header-only, но этой болезнью не страдает? Или я неправильно понял?
Прежде, чем использовать библиотеку, я всегда смотрю, насколько она влияет на размер, это хорошо коррелирует с качеством в остальных аспектах (потребление памяти, поддержка и т.п.). Например, libcurl при статической линковке и отправке жалкого GET-запроса даёт +5 к
силеразмеру в мегабайтах, а авторы выкладывают бинари под Амигу (реально у них настроен CI/CD под это дело), но не выкладывают собранный.libпод VC (которая, можно сказать, и есть разработка на плюсах виндовых приложений). Мне кажется, это как-то связано. При этом, libcurl у всех на слуху, и его критики я нигде не видел. Это к тому, что рекомендую эту эвристику.Так вот, прежде, чем выбрать fmtlib (вместо
std::format), я проверял простое форматирование сint’ом и сложное со всякими датам-временами — первое добавляет размер умеренно, второе — уже побольше. Собственно, так и должно быть, по моему мнению — слоган языка, насколько я помню, «ты платишь только за то, чем пользуешься». И выбрасывать ненужное должен линкер, автоматически. А не как в libcurl, где официально рекомендуется делать это руками.

Janycz
17.08.2026 07:49Заставить при наличии
[[nodiscard]]обрабатывать КАЖДЫЙstd::unexpected? Нет.Это невозможно в силу определения класса std::expected. Как обработать каждый std::expected<T, E>, где E идейно бесконечен, например, строка или вектор (как бы в силу ограничения возможного максимального размера памяти, E, на практике, конечен)?

ImagineTables
17.08.2026 07:49Невозможно составить список всех
std::unexpected’ов, возвращаемых функцией?
Janycz
17.08.2026 07:49Вообще говоря, да. В частности, если у вас есть библиотека без исходного кода, где есть только .lib/.a + .h.
Еще можно привести вот такой пример: [[nodiscard]] std::expected<std::string, Utf8DecodeError> utf8_to_cp1251(std::u8string_view sv). Мы получаем либо корректную строку в кодировке Windows-1251, или экземпляр класса Utf8DecodeError, который содержит в качестве одно из полей строку std::string -- то, что получилось раскодировать (например, где все непредставимые символы заменены на '?' или строку до первого непредставимого символа -- дизаин здесь может быть разным). При такой реализации составить список всех std::unexpected невозможно.

ImagineTables
17.08.2026 07:49Есть два варианта: считать, что это пересечение границы unsafe-safe, и не делать ничего, либо сделать из .lib подобие crate’а, включив в него всю необходимую для безопасной компиляции информацию.
В Rust’е если ты не обработал все возможные ошибки, код не скомпилируется. И исключения не нужны. Было бы здорово иметь в C++ возможность писать так же, и насколько я понимаю,
std::expectedэто как раз шаг в нужном направлении. Но если его добавили в C++23, то в C++29 я имел ожидания, что всю схему доведут до результата. Если не нравится именно[[nodiscard]], то извините, я не дизайнер языков. Я прикладник и мне нужен режим, в котором компилятор ругался бы на все необработанные мной ошибки. В этом суть моего разочарования. Включить такой режим для отдельной функции атрибутом — меня бы устроило.
Janycz
17.08.2026 07:49И исключения не нужны.
Я считаю, что нужны. Ибо
result.unwrap()паникует, где result это Result<T, E>, если result содержит ошибку. Лучше, чтобы в этом случае бросалось исключение. Ибо панику можно обработать только глобально, а исключение -- локально.

Nemoumbra
17.08.2026 07:49Запретить
noexcept(false)для деструкторов? ДА!!!Есть разница между деструкторами и оператором delete.

HiItsYuri
17.08.2026 07:49Заменить const на mut? Нет.
Буквально половину кода на плюсах переписать.
Дать из constexpr безопасный доступ к памяти и диску? Нет.
Дефайн безопасный. Доступ к памяти уже дали, но только в скоупе constexpr, емнип.
раздувает бинарь на сотни килобайт в релизе.
Да плевать уже на эти килобайты. Если хотите байтоебить - пишите под себя. Почему стандартная библиотека должна удовлетворять желаниям 0.001% юзеров языка?
В то время как прототип (ц)
Ну это уже совсем глупость, вы их 90ых пишете? Это разные языки с разной нишей.
Я бы начал улучшать std::format с этого. А продолжил бы поддержкой именованных аргументов в компайл-тайме
Начните с пропозола. Пиздеть не мешки ворочать.

Nemoumbra
17.08.2026 07:49В C++29 решено было убрать это неопределённое поведение, на этапе компиляции проверять и запрещать писать
noexcept(false)наoperator delete.А почему не полностью запретили здесь какие либо noexcept'ы, кроме true? Ведь если он у вас условный - значит, вы допускаете, что он может быть для некоторых шаблонов false (а это, вы говорите, уб).

HiItsYuri
17.08.2026 07:49Разломает шаблонный код например.

Nemoumbra
17.08.2026 07:49И хорошо, что разломает. UB ловить во время сборки - одно удовольствие.
Я надеюсь, вы поняли, что я говорю не про все условные noexcept, а про условные noexcept на операторе
delete?Ну или, допустим, мы потребуем CE в случае, если аргумент условного noexcept'а вычислится в false.

HiItsYuri
17.08.2026 07:49SFINAE не разломается? Честно - придумать нормальный пример не могу. Но все что приходит в голову это шаблоны и их черная магия, где я не особо силен. Вообще эта дискуссия не особо имеет смысл мне кажется - я уверен на 99% что в пропозале изменения указано, почему именно так, а не иначе. Но проверять мне лень.

Nemoumbra
17.08.2026 07:49А вот не указано. Там вообще ничего нет про условный noexcept в шаблонах.
Что касается сфинае, то выражение внутри noexcept в нём не участвует))))))
Начиная с C++20 в список мест, где это работает, докинули условный explicit, а условного noexcept там как не было, так и нет. Вот такой добрый и хороший язык.

Nemoumbra
17.08.2026 07:49А нет ли у комитета желания собрать ещё и unspecified bahavior в одно место? Там такие приколы иногда ловятся... Тот же оператор delete (обычная версия vs версия с size_t хинтом из C++14 - unspecified, кто вызовется, если не class scope).

eao197
17.08.2026 07:49Антон, спасибо за подобные рассказы.
А по поводу паттерн-матчинга что-нибудь слышно?

antoshkka Автор
17.08.2026 07:49Была попытка успеть втащить его в C++26, но не успели... Теперь неспешно будем втаскивать в C++29

eao197
17.08.2026 07:49Была попытка успеть втащить его в C++26, но не успели…
Это я помню.
Теперь неспешно будем втаскивать в C++29
Хоть какие-то подвижки есть? Или большинству это вообще не интересно?

antoshkka Автор
17.08.2026 07:49Сейчас "мячик на стороне" авторов идеи - документ на pattern matching не обновлялся с предпоследней встречи. Собственно по этому на последней встрече эта тема не обсуждалась
Заинтересованных людей много, ставлю на то, что активность скоро возобновится

x2v0
17.08.2026 07:49Саттеровский cpp2/cppfront как-нибудь обсуждался? или это тупик, или он никакого отношения к новым стандартам не имеет?

eao197
17.08.2026 07:49Саттер сам недавно суммировал статус собственного cppfront здесь (не так уж и много слов на английском с ссылками).

vsting
17.08.2026 07:49Лучшая идея для Си и Си++ это создать единый стандартный пакетный менеджер и сборщик как у Rust, Ruby и у других современных языков.

antoshkka Автор
17.08.2026 07:49Тут пошли немного другим путём. Вместо стандартизации одного пакетного менеджера, стандартизуется формат завивсимостей и конфигурирований между пакетниками - Common Package Specification. В результате любая сборочная система сможет потреблять артефакты любого пакетника.
Таким образом получится объединить и проприетарные системы сборки, и не ввязываться в бессмысленный спор "кто станет единым стандартным пакетным менеджером"

sceptizator
17.08.2026 07:49Пакетный менеджер?
А чем уже сейчас не устраивает CMake + conan?Причины выкинуть autoconf, vcpkg это понятно, там плохо с кроссплатформой.
Но CMake + conan вроде неплохо работают везде, не только лишь под Linux, Windows и macOS

sceptizator
17.08.2026 07:49Именованные параметры в вызовах функций?
get/set properties?
Возможность разрешить перегружать оператор точка?
extension methods?
Да просто возможность задать явно порядок для решения Static Initialization Order Fiasco?
Да не, ну кому нужны подобные благоглупости!
Вот std::constant_wrapper - вот то самое, чего прям всем нам очень не хватает!
И да, список constinit, constexpr, consteval - он же явно неполный. Можно же еще рассмотреть constdecl, constalloc, constforward, friendconst, virtualconst, constdelete, autoconst, staticconst, constextern, constintrinsic, constoverride (гулять так гулять, constылей мало не бывает!),
Playa
Какой-то вынос мозга если честно. Почему так?
Зачем в данном случае нужна эта прослойка с
std::optional, если всё равно копируем значение?cache.lookup(key, 42)было бы ещё проще.antoshkka Автор
Так получается более логично, чем в остальных случаях. Смотрите, давайте сделаем классический non virtual interface:
Если позвать `derived.call_me_impl(0);`, то всё должно отработать нормально - просто зовётся функция класса, надо на ней проверить `pre(x >= 0)`.
Когда вызов будет происходить через `call_me` базового класса, то надо проверить контракт на `call_me`, а потом контракт `Derived::call_me_impl`
С non virtual interface всё крайне логично. Вот только этот паттерн немного громоздкий (зачастую надо дополнительную функцию писать для каждой виртуальной) и люди его сокращают до:
Поведение получаем то же, что и с non virtual interface