После чтения несколько недавних статей – раз, два, три – и, главным образом, комментариев к ним, задумался о том, а как бы лично мне хотелось чтобы C++ развивался дальше. Результатом этих размышлений и стал данный текст. Признаю, что он больше бы подошел для личного блога, т.к. здесь не более чем праздные фантазии оторванного от мейнстрима провинциала. Но все-таки решил вынести свои несбыточные грезы на широкую публику, уж простите эту нескромность.

Возможно, самым практичным был бы вариант, когда C++ окончательно уходит на обочину и повторяет судьбу COBOL-а – где-то как-то тихо и мирно живет себе за счет многолетнего легаси, которое приносит деньги и которое никто не решается переписать на чем-то модном и молодежном. А новые стандарты не столько пытаются модернизировать язык, сколько поддерживают его на плаву.

Но кажется, что комитет выбрал для C++ совсем другой сценарий развития. Свидетельством чему является включение в стандарт такой странной (на мой деревенский взгляд) штуки, как модули. Раз модули в язык добавили несмотря на то, что это делит и сам язык, и его экосистему, на “до и после” на долгие годы, а то и десятилетия, значит люди из комитета верят, что у C++ эти самые десятилетия бодрого развития и широкого применения есть.

OK, допустим, что цель в том, чтобы C++ счастливо и успешно прожил еще 40 лет, постоянно развиваясь и адаптируясь к новым условиям. Что для этого нужно сделать сейчас? Важный дисклеймер: на мой сугубо субъективный взгляд.

Изменение метода наполнения стандарта

Уже три десятилетия наблюдаю за тем, как сперва формируется текст стандарта, а потом, когда-нибудь, разработчики компиляторов реализуют то, что в этом тексте написано. Может быть. А может и нет.

В итоге C++ разработчики имеют дело даже не с двумя, а с тремя разновидностями C++:

  • то, что описано в стандарте, но еще нигде и никак недоступно (а когда будет доступно – хз);

  • то, что есть в самых свежих версиях компиляторов. Возможно сырое. Возможно работающее по разному в разных компиляторах;

  • то, что есть в тех компиляторах, которыми приходится пользоваться. Собственно, это и есть реальный С++. Единственно доступный для тех бедолаг, которые, как и я, делают разные проекты на разных платформах и не могут сидеть на самых свежих версий одного единственного компилятора под одной единственной ОС.

Понятное дело, что у комитета своя работа, у разработчиков компиляторов и стандартных библиотек своя. Понятное-то понятное. Но подзадолбавшее изрядно. Как будто комитет занял позицию “мы только пишем стандарт, к тексту стандарта претензии есть?”

ИМХО, ситуацию можно было бы кардинальным образом переломить, если видоизменить текущую train model, практикуемую комитетом, а именно:

  • когда конкретный пропозал признают пригодным для включения в стандарт, то его не включают в стандарт сразу, а переводят в специальную категорию. Пусть эта категория называется technical specification;

  • пропозалы, перешедшие в категорию technical specification, принимаются компиляторостроителями к реализации;

  • когда technical specification успешно воплощается в жизнь в двух компиляторах, он включается в текст следующего стандарта.

Такая схема преследует две цели:

  • в процессе реализации могут быть обнаружены подводные камни или даже фатальные недостатки. Но т.к. фича еще не отлита в граните в тексте стандарта, то ее можно поменять или вообще откатить полностью;

  • выход стандарта означает, что хотя бы где-то фичи из стандарта уже есть.

При этом такой подход не мешает тем счастливчикам, которые могут сидеть на самых-самых свежих версиях компиляторов: как только очередной technical specification оказывается доступен в условном gcc или clang любители “быть на переднем крае” сразу же могут опробовать новую фичу.

Т.е. вроде как train model сохраняется, но принципиально меняется результат: вместо чисто литературного произведения из категории “вот как оно хотелось бы чтобы было когда-нибудь” стандарт превращается в фиксацию того, что уже есть в реальности.

Добавление механизма отказа от совместимости с предыдущими версиями

Несколько лет назад, когда в C++20 собирались добавлять модули, уже выдвигалась идея “эпох”. Типа в начале .cpp-файла указывается версия С++, для которой в этом файле пишется код.

Но, к сожалению, эту идею в комитете похоронили.

Тем не менее, что-то подобное С++ крайне необходимо для того, чтобы язык имел возможность развиваться и не быть похороненным под грузом унаследованного за десятилетия дерьма (где-то взятого из Си, где-то приобретенного уже в самом С++ из-за продуманности действий самого комитета /да, это сарказм/).

“Эпохи” позволили бы C++ начать избавляться хотя бы от некоторых косяков. Например, от автоматического приведения числовых типов. От возможности сравнения беззнаковых и знаковых типов. От неявного приведения Си-шного вектора к указателю. От полутора десятков способов инициализировать переменную. И т.д., и т.п.

Почему это важно?

Потому что, на мой взгляд, в обозримом будущем не будет языка программирования, 100% совместимого с существующим C++ (в том числе позволяющим использовать header-only библиотеки на 3-4-5 этажных шаблонах). Таким языком может быть только сам С++, но очищенный от тех наслоений говна, которые образовались за прошедшие десятилетия.

При этом ключевой возможностью “эпох” должна быть возможность сборки в одном проекте .cpp-файлов с разными эпохами. Грубо говоря, если у нас есть 100500 файлов, написанных еще во времена C++98 и постепенно адаптированных к более-менее современному стандарту, то мы спокойно можем их в этом состоянии и оставить, а не переписывать под новый, улучшенный C++. “Эпохи” должны дать возможность написать и положить рядом еще 100500 файлов, но уже на новых стандартах.

Т.е., на мой взгляд, “эпохи” нужны для того, чтобы можно было в рамках одного проекта комбинировать исходные файлы, написанные, фактически, на разных диалектах C++. Как древних, так и свежих. Тем самым создается возможность родить таки то самый улучшенный C++, который все никак не может выбраться из множества унаследованных и слепленных как попало фич.

Думается, что содержимое .cpp-файла, написанного для свежей “эпохи”, можно транспилировать в код для более старого стандарта, тем самым не нарушая ABI (а ведь это, емнип, и было препятствием к принятию “эпох” в C++).

Еще один механизм исключений

На мой взгляд, одна из ключевых и пока что нерешенных проблем C++ в том, что в огромном количестве проектов исключения под запретом.

Т.е. вроде как исключения – это неотъемлемая часть стандарта. Но чуть ли не в половине проектов она под полным запретом. И даже там, где исключения разрешены, но есть требования к производительности и предсказуемости, их использование стараются свести к абсолютному минимуму.

И хоть я и сам являюсь убежденным сторонником использования исключений, но не могу не признать, что исключения в C++ в их существующем виде – это фиаско ?

Да, идея была крутой для конца 1980-х, однако испытания реальностью не выдержала.

Но и отказ от исключений, имхо, не вариант. Для написания надежного софта таки нужно иметь исключения (даже в Go и Rust вынуждены были сделать паники, т.к. просто возвращаемых значений не хватает). Но гораздо более дешевые и, главное, гораздо более предсказуемые, чем существующие.

Благо, попытка сделать что-то подобное уже была в виде предложения по zero-cost исключениям от Саттера, а затем и альтернативного предложения, от другого автора.

Однако, если цель в том, чтобы дать C++ еще 40 лет успешного развития, комитету, имхо, требуется сделать еще один подход к снаряду.

И здесь очень бы в тему оказались “эпохи”, описанные в предыдущем пункте. Тогда новый тип исключений можно было бы применять только в файлах, написанных под соответствующую “эпоху”.

Сразу скажу, что не знаю, как подружить старый код, в котором бросаются старые исключения, с новым кодом, в котором ловятся только новые исключения. Вот не знаю, не думал. Да и вряд ли моей компетенции хватит, чтобы что-то придумать.

Но зато я убежден в том что:

  • продолжать жить в разделенном надвое мире “C++ без исключений вообще” и “C++ с исключениями” нельзя;

  • полностью отказаться от исключений в пользу каких-нибудь std::expected также нельзя;

  • существенно улучшить существующие исключений (т.е. сделать их быстрыми и предсказуемым) вряд ли возможно. Раз уж за столько-то лет не исправили, то глупо надеятся.

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

Заключение

В современном C++ куча проблем и данная заметка даже не претендует на попытку найти решение для хоть сколько нибудь заметной их части. Я всего лишь хочу подчеркнуть, что есть несколько принципиальных вещей, без которых C++ обречен стать еще большим монстром и, в итоге, погибнуть под тяжестью собственной сложности.

ИМХО, у C++ сейчас ситуация, как в анекдотах про PL/1 полувековой давности: “У нас получился слишком сложный язык, что мы может с этим поделать? А давайте добавим в него еще пару фич!”

Поэтому путь к дальнейшему успешному развитию C++ лично я вижу в том, чтобы внедрить в язык механизм “эпох” (“диалектов”), позволяющих объединять в рамках одного проекта как старый код, написанный на диалекте C++ с сохранением совместимости с седой древностью, так и новый код, в котором можно не оглядываться на кучу старого говна и не нужно утешать себя тем, что совместимость с чистым Си когда-то была ключевым фактором успеха.

Но этого мало. Комитет должен отойти от практики публикации опережающих реальность спецификаций. Вместо того, чтобы декларировать свои фантазии и заставлять обычных разработчиков “от сохи” ждать воплощения этих фантазий в компиляторах, в стандарте нужно фиксировать то, что уже есть и применяется. А смелые фантазии претворять в жизнь через эксперименты на кошках technical specifications.

Оба два эти момента, как по мне, являются ключевыми.

Ну а третий момент, связанный с исключениями, важен потому, что сложно предсказывать светлое будущее для языка, коммьюнити которого уже много лет, фактически, разорвано на две части. Тут уж либо нужно делать два разных С++а и развивать их по-разному, либо пытаться объединить за счет более дешевых и предсказуемых исключений.

PS. Я не тактик, я стратег © поэтому на конкретные вопросы “А как…” ответить смогу лишь фразой “я не тактик, я стратег” ?

Комментарии (130)


  1. rsashka
    11.04.2026 06:22

    то, что есть в тех компиляторах, которыми приходится пользоваться. Собственно, это и есть реальный С++. …

    На самом деле скорее всего есть четыре, а не три типа С++. Четвертый вид С++идет идет следующим и называется “то, что реально пользуются в продакшене”. Обычно это возможности, которые отстают от третьего пункта на несколько поколений стандарта. Возможности, у кого известны все подводные камни и нюансы и есть наработана легаси практика.

    А, нет. Есть еще и пяты тип С++ - это весь объем легаси кода, который тянет за собой обязательное требование по обратной совместимости со самыми старыми версиями и именно этоот пятый вид и тормозит развитие С++.


    1. rukhi7
      11.04.2026 06:22

      С++ без обратной совместимости это, наверно, C#.


      1. Siemargl
        11.04.2026 06:22

        Нет. Не тот уровень производительности.


        1. rukhi7
          11.04.2026 06:22

          когда в нативный код компилируется, уровень производительности не отличается. Но такой компилятор доступен, конечно, далеко не всем.


          1. Siemargl
            11.04.2026 06:22

            На самом деле нет.

            Но это уже тонкости, доступные да, не всем.


      1. a-tk
        11.04.2026 06:22

        На самом деле в C# ломающих изменений было, пожалуй, не больше чем в C++ (я сходу вспоминаю лишь два граничных случая, которые никогда не были в проде в исходном их виде).

        А концептуальных расширений - примерно столько же.


  1. rsashka
    11.04.2026 06:22

    Типа в начале .cpp-файла указывается версия С++, для которой в этом файле пишется код. Но, к сожалению, эту идею в комитете похоронили.

    Идея привязки кода к разным версиям стандарта существует до сих пор и частично прорабатывается в профилях безопасности. Просто это привязка реализуется не к нумерации стандарта С++, а к его возможностям, которые не связанных одной версией, что гораздо более правильно, как мне кажется.


  1. satix
    11.04.2026 06:22

    Перед тем, как добавлять эпохи, надо быть уверенным, что для новых проектов вообще будут выбирать современный С++ хоть сколько нибудь часто, в чем я оооочень сомневаюсь.


    1. eao197 Автор
      11.04.2026 06:22

      Даже если на C++ в ближайшие 40 лет будут только сопровождать существующие проекты, то все равно выигрыш от эпох будет, т.к. сопровождение – это не только баг-фиксинг в уже написанном коде, но и дописывание новых фрагментов. И новые фрагменты лучше дописывать на более современном диалекте C++.


      1. satix
        11.04.2026 06:22

        Новые фрагменты надо писать в стиле проекта, чтобы он легко "читался". Если ты в проекте видишь солянку от void* до вариадиков, это зло.

        Так что я стою на своем: отдельные библиотеки, покрытые тестами, можно писать как душе угодно. Пока они хорошо работают, их все равно читать никто не будет. А новый С++ не заточен на читателей, он для писателей.


        1. SpiderEkb
          11.04.2026 06:22

          К сожалению, в больших проектах с большим временем жизни постоянно приходится возвращаться к написанному ранее - тут через три года безупречной работе в проде вдруг вылез какой-то баг (совершенно экзотический, никому в голову не приходило что звезды вот так встанут), тут бизнес-логика изменилась, тут с ростом объема данных производительности не хватает и надо что-то придумывать... И вот любой писатель, вынужден сначала быть читателем...


          1. satix
            11.04.2026 06:22

            Я об этом и говорю. Выделяешь тонну времени, заново разбираешься, заново с матюками отлаживаешь "современный модный код", заново покрываешь тестами, опять не лезешь.
            И все это только для того, чтобы почесать свое/комитета эго.


        1. eao197 Автор
          11.04.2026 06:22

          Новые фрагменты надо писать в стиле проекта, чтобы он легко “читался”.

          Т.е. если проект стартовал в 1990-е и был написан в стиле “ООП головного мозга”, то в таком же стиле он и должен дописываться в 2030-ом?


          1. SpiderEkb
            11.04.2026 06:22

            Если он хорошо читается и удовлетворяет требованием производительности - почему нет?

            Основная проблема в том, что порой чтобы понять что именно делает код из (условно) 100 строк вам надо продраться через 1000 (опять условно) строк другого кода, содержащего десяток слоев абстракций. И не дай бог, если проблема (дефект бизнес-логики, дефект производительности...) окажется где-то там...


            1. eao197 Автор
              11.04.2026 06:22

              Если он хорошо читается и удовлетворяет требованием производительности - почему нет?

              Потому что прогресс не стоит на месте. Можно продолжать писать вложенные for-ы на страницу, а можно написать одну-две строчку на ranges.


              1. SpiderEkb
                11.04.2026 06:22

                Если эти 1-2 строчки будут работать быстрее - почему нет?

                А если это потянет 5 уровней абстракций и кучу выделений-освобождений памяти в результате которых упадет производительность - точно нет.


                1. eao197 Автор
                  11.04.2026 06:22

                  Если эти 1-2 строчки будут работать быстрее - почему нет?

                  Потому что так думает тов. @satix

                  Новые фрагменты надо писать в стиле проекта, чтобы он легко “читался”.


              1. Kyoki
                11.04.2026 06:22

                И отправить этот код и все, что его хоть как-то касается, тестерам на перетест...


                1. eao197 Автор
                  11.04.2026 06:22

                  Как будто другие типы изменений не нуждаются в перетестировании…


                  1. Kyoki
                    11.04.2026 06:22

                    Нуждаются, но, ведь, разговор идет о перписывание работающего, протестированного, устривающего по скорости кода на "новый С++" ради "прогресс не стоит на месте".


                    1. eao197 Автор
                      11.04.2026 06:22

                      разговор идет о перписывание работающего, протестированного, устривающего по скорости кода на “новый С++” ради “прогресс не стоит на месте”.

                      Не знаю кто вел разговор об этом, но точно не я.

                      Я изначально говорил про дописывание новых фрагментов. Типа была софтина, которая интегрировалась с внешними системами S1, S2, S3, …, SN. Теперь появилась новая внешняя система S(N+1) для интеграции с которой нужно дописать новый модуль (или несколько).

                      Хорошо, если этот модуль вы можете написать не на C++. Но бывает так, что в реализации этого модуля вам нужно переиспользовать уже существующие C++ные библиотеки. В таком случае C++ адекватный выбор, но старый код вы будете только использовать в новом модуле, который вам предстоит написать.


                  1. SpiderEkb
                    11.04.2026 06:22

                    Ну есть разница по объему и времени тестировании между регресс-тестом изменений и полным ретестом.


          1. Siemargl
            11.04.2026 06:22

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


  1. rsashka
    11.04.2026 06:22

    На мой взгляд, одна из ключевых и пока что нерешенных проблем C++ в том, что в огромном количестве проектов исключения под запретом. Да, идея была крутой для конца 1980-х, однако испытания реальностью не выдержала.

    Мне кажется, что исключения так и остаются самой крутой штукой С++ и выдержали испытания, так как их функционал присутствует во всех языках программирования, даже тех, кто от них якобы отказалася.

    Но сложности с их использования действительно есть, но как правило это не проблема С++ как такового, а проблема интерфейса между компонентами, ведь extern “C” ничего не знает об исключениях. Поэтому не помешал бы какой нибудь дополнительный механизм их проброса исключений через интерфейсы.


    1. eao197 Автор
      11.04.2026 06:22

      но как правило это не проблема С++ как такового

      Это проблема как только у вас исключения начнут возникать в hot path.


      1. rsashka
        11.04.2026 06:22

        А кто вам мешает именно в этом месте сделать другой способ обработки ошибок, например на возвращаемых значениях?


        1. eao197 Автор
          11.04.2026 06:22

          Мешает то, что узнать о вылете исключения в hot path вы можете только постфактум. Т.е. написали, отладили, запустили в прод, спустя какое-то время обнаруживаете, что на некоторых специфических нагрузках у вас производительность падает на порядок. Выясняете и видите, что в этих нагрузках возникают исключения, о которых вы даже не подозреваете. Например, какой-то клиент в экзотических ситуациях шлет некорректные данные, разбор этих данных ведет к исключениям, исключения роняют вам производительность. Но до этого с такими некорректными данными вы не сталкивались.

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


          1. rsashka
            11.04.2026 06:22

            Вы не ответили на заданный вопрос.

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


            1. eao197 Автор
              11.04.2026 06:22

              Вы не ответили на заданный вопрос.

              Ответил. Я вам прям расписал случай из практики. Не единичный.

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

              У вас тоже есть возможность подумать и не писать глупости, но…


            1. SpiderEkb
              11.04.2026 06:22

              Приложение не имеет права "вот так просто завершиться". Любая ошибка должна быть зафиксирована и обработана. Для этого вы всегда должны четко знать где она случилась и почему - ошибка в программе, ошибка в логике, ошибка в данных...


              1. eao197 Автор
                11.04.2026 06:22

                Приложение не имеет права “вот так просто завершиться”

                На самом деле все зависит от приложения. В некоторых случаях не только имеет, но и должна. Это т.н. принцип fail fast или let it crash.

                Но не любое и не всегда.


                1. SpiderEkb
                  11.04.2026 06:22

                  Нет. Идеально - в любой самой критической ситуации должно быть управляемое завершение с фиксацией причины для последующего расследования. А это точно не crash.

                  Потому что когда на проде в фоне работает 100500 заданий и одно из них крешнулось, сопровождение сразу об этом должно знать и, возможно, предпринять какие-то действия. И должна быть фиксация что где и почему упало. Ибо если падение повлекло какие-то серьезные последствия для бизнеса, потом будет "комиссия по инцидентам" с расследованием ситуации.

                  Последствия креша могут быть на несколько порядков серьезнее чем "не успел сохранить в файле последние 5 минут работы".


                  1. eao197 Автор
                    11.04.2026 06:22

                    Нет.

                    Отучаемся говорить за всех.

                    Потому что когда на проде в фоне работает 100500 заданий и одно из них крешнулось, сопровождение сразу об этом должно знать и, возможно, предпринять какие-то действия.

                    Когда на проде работает 100500*1000 заданий и 1000 из них каждую секунду крашатся из-за того, что общаются с внешним миром посредством разного качества инструментов, то это 1000 подробных отчетов в секунду нафиг никому будут не нужны.

                    Вы смотрите на мир из своего очень специфического угла, а этот мир очень и очень разнообразен. В нем есть и задачи, которыми занимаетесь вы (и для которых озвучиваемые вами требования актуальны), и еще туева хуча задач, о которых вы никогда и не слышали.


                    1. SpiderEkb
                      11.04.2026 06:22

                      В том и дело что задания у нас крашатся в самых исключительных случаях.

                      А 99% случаев ошибки перехватываются и обрабатываются. Т если есть возможность продолжать работу - работа продолжется.

                      Условно говоря, если где-то случилось деление на 0 потому что в данных попался 0 там, где его быть не должно - ошибка будет зафиксирована подробно - "там-то и там-то некорректные данные - 0"

                      Аналогично - переполнение (у нас переполнение вызывает системное исключение).

                      А вот когда начинает систематически крашится какой-то конкретный модуль - тут уже сопровождение обычно откатывает последний патч (если началось после установки нового патча - с патчем всегда идет "план отката").

                      есть и задачи, которыми занимаетесь вы (и для которых озвучиваемые вами требования актуальны), и еще туева хуча задач, о которых вы никогда и не слышали

                      Уверен что так и есть. Хотя за... 35 лет в разработке много чем занимался в разных областях и много чего повидал.


                      1. eao197 Автор
                        11.04.2026 06:22

                        В том и дело что задания у нас крашатся в самых исключительных случаях.

                        У вас – да. У других может быть сильно иначе. Поэтому не нужно на все смотреть через призму собственных задач.


                      1. SpiderEkb
                        11.04.2026 06:22

                        Я смотрю сквозь призму опыта. Мне не кажется хорошим стилем просто крашить задачу без подробного объяснения причин в стиле "Что-то пошло не так...".

                        Для меня обработка ошибок всегда была более сложным местом, чем реализация логики.


                      1. eao197 Автор
                        11.04.2026 06:22

                        Мне не кажется хорошим стилем просто крашить задачу

                        Повторю еще раз: для вас. Между тем есть целый подход к обспечению надежности под названиями fail fast или let it crash.


              1. rsashka
                11.04.2026 06:22

                std::set_terminate ?


              1. Kahelman
                11.04.2026 06:22

                Разработчики Erlang с вами не согласятся ….


        1. SpiderEkb
          11.04.2026 06:22

          А так и делаем. Структурированная ошибка называется.

          Беда в том, что в этом самом hot path практически все...


      1. Siemargl
        11.04.2026 06:22

        Во всех учебниках написано, что исключения не надо применять для реализации логики, только для обработки ошибок.

        Используйте setjmp :адский смех:


        1. eao197 Автор
          11.04.2026 06:22

          Во всех учебниках написано, что исключения не надо применять для реализации логики, только для обработки ошибок.

          А кто говорил о логике на исключениях?

          Простейшая ситация: класс, конструктор которого получает int, но этот int не должен быть отрицательным. Грубо говоря, int-ы используются в вычислениях, в которых промежуточные значения могут быть отрицательными, но результат – либо 0, либо положительное. По результатам вычислений создается новый объект, который в конструктор int и получает.

          class calculation_result {
            int _values_to_handle;
            ...
          public:
            calculation_result(int values_to_handle, /* другие аргументы */)
              : _values_to_handle{ values_to_handle }, /* другие члены класса */
            {
              if(_values_to_handle < 0) throw std::runtime_error{ ... };
              ...
            }
            ...
          };
          

          И вот у нас в программе calculation_result создаются миллионами штук. Но на каких-то данных выявляется ошибка в наших вычислениях из-за которой values_to_handle все-таки отрицательный. Конструктор бросает исключение. Это исключение где-то выше ловится.


          1. Siemargl
            11.04.2026 06:22

            Все же это логика.

            Тогда ошибка должна сформировался не на единичный объект, а на блок вычислений. Я бы так спроектировал


            1. eao197 Автор
              11.04.2026 06:22

              Все же это логика.

              Для меня “логика на исключениях” – это все-таки другое. Например, когда принудительный выход из рекурсии делается пробросом специального исключения, которое где-то выше ловится именно как признак того, что рекурсия была специальным образом прервана.


          1. Kahelman
            11.04.2026 06:22

            И что вы можете сделать? Если у вас в данных бардак, и данные которые не могут существовать попали в логику?

            Допустим у вас температура в кельвинах а вы там отрицательное значение передали. И что делать приложению?


            1. eao197 Автор
              11.04.2026 06:22

              И что вы можете сделать?

              Бросить исключение.

              Если у вас в данных бардак, и данные которые не могут существовать попали в логику?

              Что значит “данные попали в логику”?

              Допустим у вас температура в кельвинах а вы там отрицательное значение передали. И что делать приложению?

              Это вы у меня спрашиваете? А почему у меня?

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


              1. rsashka
                11.04.2026 06:22

                У вас класс принимает бизнес данные в конструктор, а это особенный вызов, который физически не может вернуть информацию об ошибке никаким другим способом, кроме как вызвать исключение.

                Делайте или статический фабричный метод или проверяйте данные не в конструкторе, а с помощью обычной функции. Другими словами, прерывания тут совершено не причем.


                1. eao197 Автор
                  11.04.2026 06:22

                  У вас класс принимает бизнес данные

                  Слова-то какие. А в std::vector::at тоже бизнес-данные поступают?

                  Делайте или статический фабричный метод или проверяйте данные не в конструкторе

                  Простите, вы всегда такой умный или только в разговоре со мной?

                  Показанная выше проверка в конструкторе – это, по сути, проверка контракта со стороны calculation_result. Он требует, чтобы ему давали уже проверенную информацию, но сам, ради паранои, делает дополнительную проверку. И, так уж получается, как последний бастион, ловит ошибки, которые a) не должны были бы возникать вообще и b) должны были быть проверены выше. Но не были, т.к. в коде случаются ошибки.


                  1. rsashka
                    11.04.2026 06:22

                    Вам уже написал, что это вы ССЗБ и нечего пенять на молоток при кривых руках.


                    1. eao197 Автор
                      11.04.2026 06:22

                      Вам уже написал, что это вы ССЗБ и нечего пенять на молоток при кривых руках.

                      Льщу себя надеждой, что я не более ССЗБ, чем разработчики стандартной библиотеки, добавившие метод at в std::vector.


            1. Siemargl
              11.04.2026 06:22

              И что вы можете сделать? Если у вас в данных бардак, и данные которые не могут существовать попали в логику?

              Контроль данных на входе, контракты.


          1. feelamee
            11.04.2026 06:22

            По-моему вы используете исключения для обработки ошибок. Они нужны не для этого. Исключения нужны, извиняюсь, для исключительных ситуаций. Обычно это что-то связанное с IO. В вашем же примере вы их используете чтобы проверить “правильные ли данные мне передали”. Это должно быть обработкой ошибки, раз вы не доверяете тому кто ваам их передает. А еще лучше - используйте систему типов для предотвращения возможности таких ошибок, например unsigned.


            1. eao197 Автор
              11.04.2026 06:22

              По-моему вы используете исключения для обработки ошибок.

              А метод std::vector::at использует исключения для чего?

              Исключения нужны, извиняюсь, для исключительных ситуаций.

              А получение методом std::vector::at неверного индекса – это исключительная ситуация или где?

              А еще лучше - используйте систему типов для предотвращения возможности таких ошибок, например unsigned.

              ЕМНИП, для целой кучи алгоритмов над матрицами и векторами удобно использовать именно знаковые индексы, потому что эти самые индексы нужно двигать туда-сюда. Настолько удобно использовать именно знаковые, что даже в стандартную библиотеку C++ была добавлена функция std::ssize. И если при обработке знаковых индексов программист допускает ошибку и эта ошибка не отлавливается тестами, а затем проявляется в работе, то конвертация из знакового в беззнаковый проблему не решает от слова совсем.

              И да, байти про то, что нужно тестировать, рассказывать бесполезно.


              1. feelamee
                11.04.2026 06:22

                А метод std::vector::at использует исключения для чего?

                это нужно спрашивать у людей который его предложили в стандарт. Я его ни разу не использовал и плохо представляю ситуацию где это нужно.

                А получение методом std::vector::at неверного индекса – это исключительная ситуация или где?

                в моем понимании это должен быть ассерт (т.е. ошибка программиста), что и делает std::vector::operator[]. Но кто-то может использовать это и как исключительную ситуацию, если для него это подходящее поведение.

                то конвертация из знакового в беззнаковый проблему не решает от слова совсем.

                по-моему отлично решает. С беззнаковым вам компилятор говорит - иди проверь что у тебя правда беззнаковый и конвертируй. Т.е. программист будет обязан это сделать. Со знаковым он просто забудет об этом или будет уверен что не допустил ошибку в предыдущем коде и передаст как есть. Т.е. по сути контроль ошибки переходит из конструктора в код вызывающий его.


                1. eao197 Автор
                  11.04.2026 06:22

                  Я его ни разу не использовал и плохо представляю ситуацию где это нужно.

                  Зато я использую регулярно и не менее регулярно он ловит ошибки в коде. Более того, это настолько хорошо зарекомендовало себя на практике, что в C++26 будет т.н. hardering для стандартной библиотеки.

                  в моем понимании это должен быть ассерт (т.е. ошибка программиста)

                  И? Вот есть два программиста: Вася, который вычисляет индекс, и Ваня, который пишет реализацию std::vector. Вася допустил ошибку, в результате к Ване в vector::at приходит недопустимый индекс. И что делать Ване? Ведь ошибку допустил не он.

                  Классический assert работает только в DEBUG сборках, но превращается в ничто в RELEASE. Значает ли “ваше понимание”, что в RELEASE данные на входе в std::vector::at проверять не нужно?

                  С беззнаковым вам компилятор говорит - иди проверь что у тебя правда беззнаковый и конвертируй.

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

                  Кроме того, у меня складывается ощущение, что вы придерживаетесь той точки зрения, что код можно протестировать и найти/устранить все допущенные ошибки в ходе этого процесса. Грубо говоря “пиши правильно и ошибок у тебя не будет”.

                  Увы, это не работает на практике.


                  1. feelamee
                    11.04.2026 06:22

                    Более того, это настолько хорошо зарекомендовало себя на практике, что в C++26 будет т.н. hardering для стандартной библиотеки.

                    насколько я знаю, std::vector::at не имеет ничего общего к hardening. Hardening это как раз ассерт в std::vector::operator[], а не исключение в at.

                    Зато я использую регулярно

                    Зачем вы его используете? В какой ситуации?

                    И что делать Ване? Ведь ошибку допустил не он.

                    Ваня должен декларировать интерфейс - возвращает элемент если индекс корректный и, например, UB если неккоретный. Но исключение тоже выглядит как валидный вариант (std::vector::at). Так же выглядит и возврат std::optional. Это лишь вопрос реализации.

                    Значает ли “ваше понимание”, что в RELEASE данные на входе в std::vector::at проверять не нужно?

                    В at нужно, потому что в этом его смысл - таким его придумали и стандартизировали. В operator[] - не нужно. Или, если точнее, человек который компилирует/пишет код может принять решение что делать в случае assert - C++26 контракты позволяют выбрать вариант поведения. Того же эффекта можно добиться и реализацией своих ассертов.

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

                    Ну если мы пришли к тому что не верим в добросовестность своих же коллег, то нас уже ничто не защитит против *(int*)0xDEADBEEF

                    … что код можно протестировать и найти/устранить все допущенные ошибки в ходе этого процесса. Грубо говоря “пиши правильно и ошибок у тебя не будет”.

                    Нет, скорее придерживаюсь взгляда, грубо говоря, “пиши правильно и ошибок у тебя не будет, а если будут - сделаешь хотфикс".

                    Но я не уверен что это верно. Не уверен что так можно создавать надежный софт. Я довольно неопытен в разработке ПО.

                    Увы, это не работает на практике.

                    а что работает?


                    1. eao197 Автор
                      11.04.2026 06:22

                      насколько я знаю, std::vector::at не имеет ничего общего к hardening

                      Зато hardering имеет отношения к тому, что в интерфейсе vector (и не только) сперва сделали operator[] без проверки (т.е. полное доверие разработчику) и at с проверкой. Время показало, что доверие разработчику – это так себе подход, поэтому сейчас и для operator[] делают принудительную проверку.

                      Hardening это как раз ассерт в std::vector::operator[]

                      Во-первых, это уже детали. Во-вторых, это не унаследованный из Си assert, а разновидность контракта, который нельзя просто так проигнорировать. В-третьих, как уже сказал, принципиальна принудительная проверка в operator[].

                      Зачем вы его используете?

                      Для доступа к содержимому вектора или std::span.

                      В какой ситуации?

                      Когда нет уверенности в корректности имеющегося на руках индекса. Например, когда индекс приходит из вне. Или вычисляется.

                      Хотя временами от at приходится отказываться по показаниям профайлера.

                      Ваня должен декларировать интерфейс - возвращает элемент если индекс корректный и, например, UB если неккоретный.

                      Опять же, как показала практика, UB в качестве разновидности “интерфейса” – это так себе подход. Так что в Ж такие интерфейсы, как и разговоры об их допустимости (за редким исключением).

                      А если мы UB как нормальный вариант не рассматриваем, то мы в ситуации когда:

                      • есть явное нарушение контракта со стороны клиента нашего класса/метода/функции;

                      • явное нарушение контракта может быть исключительной ситуацией в ряде случаев (конструктор, перегруженный оператор, результат которого не может быть представлен условным std::optional/std::expected /в качестве примеров: operator[], operator* (для разыменования умного указателя)/). И если это исключительная ситуация, то использование исключения для информирования о ней – это нормально.

                      Кроме того, выброс исключения – это не есть “обработка ошибки” в данном случае.

                      Но исключение тоже выглядит как валидный вариант

                      Ну вот выше весь сыр бор начался с того, что кому-то не понравился выброс исключения из конструктора calculating_result.

                      Ну если мы пришли к тому что не верим в добросовестность своих же коллег

                      Здесь нет вопроса веры. Здесь есть факт того, что программисты ошибаются. Всегда. Даже когда они очень добросовестны. Особенно когда не очень.

                      Это данность. Для борьбы с которой придумывают и разные формы тестирования, и разные формы анализа, и проверки в ран-тайм.

                      Нет, скорее придерживаюсь взгляда, грубо говоря, “пиши правильно и ошибок у тебя не будет, а если будут - сделаешь хотфикс".

                      Звучит как “нужно делать как надо, а как не надо делать не нужно” ;)

                      Хотфиксы – штука, увы, неизбежная. Но если ошибка таки утекла в продакшен, то чем раньше одна будет обнаружена, тем быстрее этот самый хотфикс можно будет сделать. Проверки в ран-тайм для данных, доверия которым не много, как раз и способствует более быстрому обнаружению проблем.

                      а что работает?

                      Паранойя. Т.е. кроме тестирования (и разных форм статического/динамического анализа, если у вас есть возможность их использовать) нужны проверки в ран-тайме. Насколько вам позволяют требования по производительности и ресурсоемкости.

                      Грубо говоря, лучше начинать с использования std::vector::at, отказываясь от него только после проведенного тестирования и только в местах, где есть явная просадка производительности из-за проверок внутри at.


              1. Ryppka
                11.04.2026 06:22

                Любят люди для индексов использовать size_t, а по стандарту там должен быть знаковый ptrdiff_t…


                1. eao197 Автор
                  11.04.2026 06:22

                  По стандарту std::vector::operator[] получает индекс в виде типа size_type, который берется из аллокатора и, в большинстве случаев, является size_t.


                  1. Ryppka
                    11.04.2026 06:22

                    Видимо у нас случилось недопонимание. Под массивами я понимаю классические "сырые" массивы, для них операция индексации является сахаром для адресной арифметики: <address> + <ptrdiff_t>. std::vector -- не массив (хотя внутре у ей неонка), а инстанс класса с операцией индексации, заданной для size_type. Можно углубляться, но суть такая. И инварианты у него таковы, что отрицательные индексы бессмыслены.


                    1. eao197 Автор
                      11.04.2026 06:22

                      Видимо у нас случилось недопонимание.

                      Скорее всего. Я говорил про std::vector.


              1. Ryppka
                11.04.2026 06:22

                ssize_t все-таки для сишной традиции отрицательными значениями возвращать ошибки.


                1. eao197 Автор
                  11.04.2026 06:22

                  Не только. ssize_t и знаковые индексы удобны для прохода по вектору в обратном направлении.


            1. SpiderEkb
              11.04.2026 06:22

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

              Именно об этом и говорю. Исключение - это непредусмотренная и необработанная ошибка.

              А еще лучше - используйте систему типов для предотвращения возможности таких ошибок, например unsigned.

              Если бы все было так просто. Ошибки в данных не ловятся типами.

              Простейший пример - у вас есть сумма в валюте счета. Все суммы в коммерческих расчетах имеют тип с фиксированной точкой и хранятся в миноритарных единицах. Пусть это будет decimal(23, 0) (23 знака, 0 после запятой).

              А теперь вам надо получить эквивалент суммы в валюте счета в рублях. И положить ее в переменную того же типа - decimal(23, 0)

              Т.е. вам надо сконвертировать сумму в валюте счета в рубли по текущему курсу и проверить не превышает ли она заданное значение. Берем сумму в валюте счета, берем код валюты, берем из справочника курс, перемножаем и... Получаем системное исключение - "MCH1210 - RECEIVER VALUE TOO SMALL TO HOLD RESULT". Ну система так работает - она для коммерческих вычислений, тут переполнение является ошибкой.

              Варианта два - оставить все как есть, ловить исключение и обрабатывать. Но это нехорошо с точки зрения производительности. Значит надо использовать временну. переменную с максимальным размером - decimal(63, 0). Конвертировать в нее, потом проверять, поместится ли она в целевую и если нет - возвращать уже структурированную ошибку. Примерно так:

              dcl-s sourseCurrensy  packed(23: 0);
              dcl-s targetRoubles   packed(23: 0) inz(*hival);
              dcl-s cource          packed(23: 0);
              dcl-s intermediate    packed(63: 0);
              
              intermediate = sourseCurrensy * cource;
              
              if intermediate > targetRoubles;
                error = ...
              else;
                targetRoubles = intermediate;
              endif;

              targetRoubles инициализируется как *hival - максимально возможное значение для данного типа, в данном случае 23 девятки.

              В данном случае если присвоение вызовет переполнение, возвращаем результат как максимально возможный для данного типа + ошибку (предупреждение) что на самом дел там больше.

              Если возможны как положительные, так и отрицательные значения

              if intermediate in %range(%loval(target): %hival(target));
                target = intermediate;
              else;
                error = ...
              endif;

              %loval/%hival тут функции, возвращающие минимально/максимально возможные значения переменной.

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

              Это очень простой пример. Есть более сложные, когда данные, сами по себе валидны, но не проходят в разрезе бизнес-логики отдельно взятого процесса.


              1. feelamee
                11.04.2026 06:22

                Ошибки в данных не ловятся типами

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


                1. SpiderEkb
                  11.04.2026 06:22

                  Вам лишь нужно объявить тип, который определяет свои арифметические операции, операции перевода в другую валюту.

                  Я связан контрактом. В БД все суммы хранятся в формате decimal(23,0) которого хватает в 99.99% случаев (за исключением когда на тестовом сервере накидали каких-то совсем нереальных данных). И я не могу вернуть какой-то иной тип.
                  Я просто показал что вместо перехвата исключения

                  monitor;
                    targetRoubles = sourseCurrensy * cource;
                  on-excp 'MCH1210';
                    targetRoubles = %hival(targetRoubles);
                    error = ...
                  endmon;

                  С точки зрения производительности рациональнее использовать дополнительную проверку.

                  Есть много других примеров, просто там все более сложно и долго расписывать.


          1. Nick0las
            11.04.2026 06:22

            На одной из моих работ мы искали самый удобный вариант реализации обработки ошибок. Долго обсуждали, тестировали варианты, пришли к некому решению. Наше решение было такое: всем методы, которые по логике должны вернуть результат типа result_t возвращают шаблонный класс Result<result_t>. В этом классе примерно как в std::tuple идет результат (валидный только если нет ошибки), код ошибки, и прочие метаданные ошибки (текстовое описание, …). Помимо этого был реализован макрос CALL(…) который вызывает функцию, и если она вернула ошибку - возвращает ошибку дальше через return, а если она вернула результат - возвращает его (как rvalue). И похожий макрос TRY(…) для void. Важно: возможность не инициализировать переменную а выйти из функции returnом в процессеэто расширение gcc а не возможность c++20, но мы использовали. Применение выглядело так:

            Result<result_t> MyClass::do_someting(ArgType1 const arg1, ArgType2 const & arg2) {
                if (Consts:ArgType1ImpossibleValue == arg1) {
                    return Result::error(RESULT_INVALID_VALUE, "Запрещенное значение");
                }
                TRY(chechAlive(arg2));
                auto v1 = CALL(this->prepareArg(arg1));     
                auto v2 = CALL(agr2.calculateFor(v1));
                result_t result = TRY(this->calculate(arg1, v2));
                return result; // вызовет неявное преобразование типа и вернет резултат
            }
            

            Наша реализация CALL и TRY в том числе ловила ексепшны (не потому что мы их кидали а потому что они могут кидаться std библиотекой)

            Неудобно было использовать этот инструметнтарий в конструкторе, но обойти можно так:

            static Result<void> MyClass::validate(ArgType3 const arg3) {
                if (Consts::ArgType3Limit > arg3) {
                    return Result::error(RESULT_INVALID_VALUE, "Слишком маленькое значение");
                }
                // Result<void> без ошибки вернется автоматически
            }
            
            static Result<MyClass> MyClass::safe_construct(ArgType3 const arg3) {
                TRY(validate(arg3));  //Валидируем
                return MyClass(arg3); // вызываемко конструктор без валидации внутри
            }
            


            1. eao197 Автор
              11.04.2026 06:22

              Спасибо за развернутый и интересный комментарий.

              Но у меня складывается ощущение, что это наглядный пример того, как из C++ пытаются сделать какой-то другой язык подручными средствами.


            1. NN1
              11.04.2026 06:22

              Ну то есть подход как в Rust только без удобств.

              И даже там в итоге не все так просто и многие приходят к решению упростить через https://docs.rs/anyhow

              Сегодня можно вместо Result вернуть std::expected.


            1. a-tk
              11.04.2026 06:22

              Я так понимаю над обработкой ошибок вы особо не думали, и полиморфное поведение не используете.


    1. SpiderEkb
      11.04.2026 06:22

      Увы, но использование исключений катастрофически отрицательно влияет на производительность... Вплоть до того, что на нагрузочном тестировании вам вынесут приговор - "20% ресурсов процессора уходит на работу с исключениями - переделать".

      Исключение допустимо в условиях возникновения критической, блокирующей ошибки. Когда "ой, все..." - дальнейшая работа невозможна. Но не как средство возврата обрабатываемой ошибки, подразумевающий какую-то дальнейшую логику.


      1. rsashka
        11.04.2026 06:22

        Иииии…?

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

        Поэтому лучше иметь стандартизированные исключения с возможностью их обработки в рантайме, чем в каждом случае завершать выполнение приложения по abort или с помощью хаков пытаться реализовывать перехват подобных ошибок.


        1. SpiderEkb
          11.04.2026 06:22

          Я не спорю. Больше скажу - удобнее всего когда исключения не языковые, а системные. С возможностью пробрасывать их на любой уровне стека вызовов (вплоть до за пределы своей программы тому, кто ее вызвал с уверенностью что там оно тоже будет перехвачено и обработано вне зависимости от того, на чем та программа написана).

          Но это механизм обработки критических, блокирующих ошибок. Это тяжелый механизм, который может использоваться только в тех случаях, когда терять уже нечего и производительность уже не важна, важно корректно завершение с подробной фиксацией что где и почему.

          А вот против того, чтобы использовать исключения в качестве механизма возврата любой ошибки, даже не блокирующей, которую надо просто зафиксировать и идти дальше.

          Как пример - обработка 25 000 000 записей в рамках заданного временного окна и условиях ограничения ресурсов. В 10-ти записях (условно, может быть 100, может быть 1000) возникает какая-то ошибка, связанная со специфическим набором данных в конкретной записи (скорее всего, какое-то количество таких ошибок будет всегда). Такие ошибки необходимо фиксировать для последующего ручного разбора. Но все остальные записи надо продолжать обрабатывать. Вот тут исключения категорически непригодны т.к. снижают производительность.


          1. rsashka
            11.04.2026 06:22

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

            Так именно это я и имею ввиду. Исключения нельзя использовать для обработки часто возникающих ситуаций. Их назначение - прерывать поток выполнения при возникновении не восстановимой ситуации, когда становится не важна производительность и требуется корректно обработать завершение приложения. Но если на более высоком уровне есть механизм обработки подобных ситуаций, то пусть он перехватывает исключения и продолжает работу приложения дальше (например вот так Избавляемся от ошибок Segmentation fault из-за переполнения стека в С++).

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


            1. eao197 Автор
              11.04.2026 06:22

              Другими словами, исключение, это не обработчик ошибок, а возникновение события достаточного для завершения работы всего приложения

              Выделено в корне неправильно.


              1. rsashka
                11.04.2026 06:22

                Данная фраза относится к архитектурным требованиям, которые я считаю разумными. Но вы можете использовать исключения в каких нибудь других вариантах использования. Ведь это обычный инструмент, и как его применять, только ваше дело.


  1. OlegZH
    11.04.2026 06:22

    Зачем развивать то, что должно быть разделено на несколько различных языков? Да, у таких языков будет похожий синтаксис, но задачи будут решаться различные. Например, "С++ с указателями", "С++ без указателей"; "С++ с модулями", "С++ без модулей"; ... Для программирования пользовательского интерфейса нужно что-то своё, для работы с БД — что-то другое. Зачем всё грести в один монструозный язык, который и за всю жизнь не изучишь?


    1. rsashka
      11.04.2026 06:22

      И каждого варианта свои стандарты, свои компиляторы, возможно свои особенности синтаксиса … И все это должно иметь возможность взаимодействия между сбой. Как представлю, бррр… :-(


      1. SpiderEkb
        11.04.2026 06:22

        Фактичекски это уже разделение на разные языки от одного корня. И под каждую задачу будет выбираться тот, который наилучшим образом ей соответствует. И за совместимость тут не думаю что надо так уж переживать...


    1. Siemargl
      11.04.2026 06:22

      С, C++, ObjC, С#, C^^, D. Годится? =)


      1. OlegZH
        11.04.2026 06:22

        не совсем. а что такое "C^^" и "D"?


        1. Siemargl
          11.04.2026 06:22

          C^^ это С++/CLI, если полностью, там указатели - крышечки.

          D это dlang


  1. rsashka
    11.04.2026 06:22

    Комитет должен отойти от практики публикации опережающих реальность спецификаций. Вместо того, чтобы декларировать свои фантазии …

    Мне кажется, что в С++26 включили рефлексию и контракты, просто чтобы снизить градус критики комитета. Как будто подкинули пару задач на ближайшие три года, чтобы не заниматься безопасностью и действительно важными вещами.

    Я не хочу сказать, что рефлексию и контракты вещи “не важные”. Важные, вот только статическая рефлексия типов в текущем виде напрашивалась пару десятилетий назад, а контракты предложены в кокой-то непонятной (для меня) форме.

    Было бы понятно, если бы контракты были аналогичны реализованным в Ада как элемент доказательно программирования во время компиляции для сужения значений у типов данных. Но если я правильно понял, принятый в С++26 вариант контрактов не вводит сужения значений для типов, а реализует обычные проверки условий, в том числе и в рантайм, что делает их обычной комбинацией static_assert и обычного assert. И стоило из-за этого городить огород?


    1. Nemoumbra
      11.04.2026 06:22

      В целом, да.

      Ну, почти... Это ассёрты с кастомизацией поведения. Да, не то, о чём вы говорите, но и не совсем то же самое, что было.


  1. SpiderEkb
    11.04.2026 06:22

    Ох как согласен...

    На мой взгляд, одна из основных проблем С++ в том, что туда тащат все, что на глаза попадется и до чего руки дотянутся. В результате получает крайне сложный язык, порог входа постоянно повышается, более-менее безопасно писать на нем возможно только тем, кто будучи разбуженным посреди ночи может процитировать наизусть любую главу стандарта хоть в прямо порядке, хоть задом наперед.

    И Вы не зря упомянули PL/I - он рухнул именно по причине непомерно возросшей сложности. С++ уверенно идет туда же и держится на плаву исключительно за счет накопленной огромной кодовой базы.

    Сам я начал писать на С в... 89-м, по-моему, году. Потом был "С с классами" (который очень нравился), потом С++, который нравился чем дальше тем меньше. И так до 17-го года, когда волею судеб занесло на достаточно экзотическую у нас платформу со своим стеком (да, С/С++ тут тоже есть, но это не основной язык) в разработку большой системы с высокими требованиями к надежности и производительности (АБС крупного банка).

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

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

    Но мы предпочитаем работать со "структурированными ошибками" - структурой, содержащей код ошибки + блок данных для конкретной ситуации. Такой механизм поддерживается на уровне нашей платформы и такую ошибку можно как вернуть в виде параметра, так и "выбросить" в виде системного исключения.


  1. NeoCode
    11.04.2026 06:22

    Кстати а кто нибудь знает подробности почему "эпохи" похоронили?

    Я подобную идею предлагал уже давно (в комментариях на Хабре). Кажется это было бы идеальным решением. И компиляторы долгое время все равно поддерживали бы все эпохи, но хотя бы можно было бы не беспокоиться за чистоту кода в конкретном файле: если в начале указана некоторая эпоха - можно быть уверенным, что там нет определенных языковых конструкций.


    1. eao197 Автор
      11.04.2026 06:22

      Кстати а кто нибудь знает подробности почему “эпохи” похоронили?

      Подробностей не помню, но вроде как основным препятствием был фактор ABI, мол, модули, которые будут собираться под новую “эпоху”, могут требовать более свежую stdlib, чем модули, которые собираются под старую эпоху.

      Вот здесь есть следы обсуждения: https://www.reddit.com/r/cpp/comments/gnw1pa/comment/frcet52/


      1. Siemargl
        11.04.2026 06:22

        А чего не хватает используя /std=c++2a например?


        1. eao197 Автор
          11.04.2026 06:22

          Не понял вопроса. Могли бы развернуть?


          1. Siemargl
            11.04.2026 06:22

            Ключик форсирует правила соответствующего стандарта при компиляции единицы трансляции.

            Т.е устаревшее станет запрещенным.

            Возможно, конечно просто надо это поведение компилятора ужесточить относительно текущего, сам как то не применял в обратную сторону(не разрешить новое, а запретить старое)


            1. eao197 Автор
              11.04.2026 06:22

              Я так понимаю, что логика комитета состоит в том, что старый код должен компилироваться новыми стандартами если в этом коде нет того, чтобы было явным образом удалено из языка. Грубо говоря, если был код, написанный в С++98 без std::auto_ptr, то этот код должен продолжать компилироваться и в С++11, и в C++17, и в С++26.

              Но мне хочется иметь возможность убрать из языка какие-то вещи. Например, неявные преобразования типов. Однако, тогда старый код перестанет компилироваться условным C++29. И что делать, когда есть куски кода, написанные на C++17, который никто в обозримом времени переписывать не будет?

              Можно, наверное, в проектном файле указывать что для вот этих .cpp-файлов стандарт C++17, для вот этих C++26, а для вот этих C++29. Но, как по мне, это так себе идея.

              ИМХО, лучше для всего проекта выставить, скажем, С++17, а внутри конкретных .cpp-файлов писать

              #pragma std=c++29
              


  1. Dhwtj
    11.04.2026 06:22

    C++ помогли бы

    1. editions и отрезание deprecated. Хотя не уверен, удастся ли отрезать неявные преобразования типов - врождённый дефект языка для совместимости с Си. Уронили его в детстве с лестницы, последствия удара головой.

    2. Нормальный менеджер пакетов вроде cargo

    Тогда может быть лет через 10 станет нормальным языком.


    1. eao197 Автор
      11.04.2026 06:22

      Нормальный медленно пакетов вроде cargo

      Нормальный менеджер пакетов сейчас рождается в конкурентной борьбе между vcpkg и conan.


    1. BenGunn
      11.04.2026 06:22

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

      А вот свой аналог cargo это вот то что реально нужно языку прямо сегодня.


      1. eao197 Автор
        11.04.2026 06:22

        Неявные преобразования типов можно убрать прямо сейчас.

        Что-то зашито в ДНК языка. Например, какой результат будет у сложения двух std::uint8_t? https://godbolt.org/z/o44scjv47


        1. BenGunn
          11.04.2026 06:22

          Да, именно так - зашито в ДНК. Вы не знали про integer promotion описанное в стандарте? Но если вы знаете про это и это мешает вам вы вправе отключить такое поведение и исправить возникшие ошибки при сборке. Делов на 20 минут, начать и кончить :D На мой взгляд это не делает язык хуже или как то сложней. К тому же implicit приведение типов весьма полезная вещь, а компилятор подскажет про потерю точности если таковая будет. Вы же надеюсь читаете выхлоп компилятора? Я читаю.

          К тому же все это нытье про сложность C++ напоминает мне нытье неосиляторов которые споткнулись о собственное не знание инструмента которое им ударило по лбу. Как говорил мой лётный инструктор - что бы не было залетов изучай район полётов.


          1. eao197 Автор
            11.04.2026 06:22

            Но если вы знаете про это

            то это все равно не мешает совершать ошибки

            вы вправе отключить такое поведение

            каким образом?

            Вы же надеюсь читаете выхлоп компилятора?

            Что вы понимаете под выхлопом компилятора?


            1. BenGunn
              11.04.2026 06:22

              то это все равно не мешает совершать ошибки

              Как будто тот же Rust не позволяет совершать ошибки...

              каким образом?

              Вас в гугле забанили?

              explicit конструкторы классов и семейство флагов компилятора -Wconversion переведенные в ошибки через -Werror=. Аналогично для msvs W3 и W4 семейство флагов так же переведенные в статус ошибки.

              Что вы понимаете под выхлопом компилятора?

              Вам в output вашего редактора или ide компилятор пише что он там собирает, диагностические сообщения какие проблемы если не собралось. Не видели разве такого? Там еще бывают сообщения о потери точности при преобразовании типов если таковые включены.

              Крайне рекомендую читать документацию к компилятору. Откроете для себя много нового.


              1. eao197 Автор
                11.04.2026 06:22

                Вас в гугле забанили?

                Я вам пример на godbolt привел. Вот он же с вашими ключиками -Wconversion и -Werror: https://godbolt.org/z/1e4PEboxM Хотелось бы увидеть ошибку от компилятора.

                Вам в output вашего редактора или ide компилятор пише что он там собирает, диагностические сообщения какие проблемы если не собралось.

                Некоторые под выхлопом компилятора понимают не сообщения об ошибках или предупреждениях, а генерируемый компилятором ассемблерный код.


        1. Siemargl
          11.04.2026 06:22

          Честно говоря ошибки этого класса достаточно редки.

          Я бы тогда уж смотрел на артефакты плавающих вычислений


          1. eao197 Автор
            11.04.2026 06:22

            Честно говоря ошибки этого класса достаточно редки.

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


            1. Melirius
              11.04.2026 06:22

              Гнать из С++ программистов ср...ой метлой, как говорила моя бабушка, если они этого не знают. Ну реально, как этого базового факта можно не знать???


    1. Anton_Timofeev
      11.04.2026 06:22

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


      1. buldo
        11.04.2026 06:22

        Ну да, ну да, весь мир с этим как-то живёт, а вот C++ такой особенный, что только здесь будут проблемы


        1. HardlinePeak936
          11.04.2026 06:22

          Если бы мы его понимали, но мы его не понимаем...


  1. Jijiki
    11.04.2026 06:22

    а я бы хотел язык такой стандарт языка С++(я понимаю что это почти невозможно), который схож с растом, суть в том, что в моём представляемом не существующем языке, конструкции языка типо нумерик/концепт/...всё что режет глаз элегантно как-то убрано в кишки языка и пускай язык сам решает в момент компиляции где там что, потомучто нагромождения кишок где что создаётся иногда выглядит как битва за то чтобы писать чисто, и мешает этому не только легаси, но и новшества которые надо в коде буквально как маркер описывать, и вот код уже около нечитаемый(важный нюанс чтобы были правила предсказуемости выведения это не копия раста, но типо синтаксис С++ его все фишки, но они в кишках выводятся сами правилами какими-то)

    чтобы std:: сам понимал своими правилами - да пусть эти правила будут жестокими, но чтобы я не писал уже std, чтобы модули из коробки, удобное многопоточие, все что бы было убрано в кишки и выводилось какими-то правилами касательно шумовых-маркерных конструкций так и вообще всего почти языка, чтобы писать только тип портировать что надо и оно там выводилось, а компилятор в компил тайме показывал ветку узла куда пошла генерация - тоже по каким-то правилам

    текущий С++23/26 мне нравится, но я уже присматриваюсь всё чаще на Раст, он как-то более читабельный

    а вы пробовали Раст?


    1. eao197 Автор
      11.04.2026 06:22

      Я бы хотел таких комментаторов, которые внятно доносят свою мысль разбивая текст на абзацы и предложения.


  1. KanuTaH
    11.04.2026 06:22

    Не знаю, те же эпохи - это по-моему наоборот путь в никуда. То есть я не могу в существующем модуле просто взять и применить новые фичи "по месту" точечно, я должен сначала переписать те места, которые данная эпоха "не поддерживает", ну такое себе упражнение. Если упороться в такое, то проще уж сразу писать новые модули на каком-нибудь карбоне, но желающих это делать в массовом масштабе очевидно нет.


    1. eao197 Автор
      11.04.2026 06:22

      Наличие эпох не заставляет их использовать.


    1. Siemargl
      11.04.2026 06:22

      Ну а почему бы и нет.

      Разновидность рефакторинга.

      Лишь бы свободу выбора оставляли.


    1. a-tk
      11.04.2026 06:22

      В мире .NET-а в них сходили как минимум дважды (client profile, netstandard), и, к счастью, закопали.


  1. nickolaym
    11.04.2026 06:22

    Не согласен с предложением "давайте пусть комитетчики бегут не впереди, а позади паровоза".

    Потому что именно это они делали с 2003 по 2011 год. Дожидались, когда у зоопарка крутых компиляторов и у мега-библиотек наподобие буста наберётся достаточной статистики и положительного опыта.

    А в это время рядовые программисты либо кушали блины с лопаты, либо участвовали в кембрийском взрыве "как бы ещё извратиться и хакнуть те возможности, которые есть сейчас из коробки на копролитических промышленных компиляторах".

    Это было весёлое время, но возвращаться в него я категорически не хочу!


    1. eao197 Автор
      11.04.2026 06:22

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

      При такой схеме сраные модули попали бы не в C++20 (где их нет до сих пор в нормальном виде), а в C++26 или даже C++29. Чтобы эти гребанные модули, если уж кому-то они так вперлись, смогли бы использовать все желающие. А не только те, кто пишет один проект только на одном clang-е или VC++.

      Т.е. условный no_unique_address все так же был бы одобрен в 2020-ом или 2019-ом, но в формальный стандарт был бы включен в 2023-ем или 2026-ом.


      1. trinxery
        11.04.2026 06:22

        принятый пропозал не сразу попадает в стандарт, а только после того, как появится минимум две его реализации

        Стандарт это требования к компиляторам. Вы хотите наплодить сущностей и поставить телегу впереди лошади, чтобы сначала была реализация, а потом её стандартизация. Разве это адекватно?

        Было: пропозал, обсуждение его, включение в стандарт, реализация компиляторами.

        Станет: пропозал, обсуждение, его заморозка, реализация компиляторами, включение в стандарт.

        После принятия такой чепухи слово "стандарт" потеряет всякий смысл, потому что компиляторы фичи будут реализовывать по замороженным пропозалам, а "стандарт" останется где-то в стороне, так как он всегда будет отставать от имеющихся де-факто возможностей. Если пропозалы заморожены, то де-факто они станут являться стандартом.

        В вашей идее нет ни грамма смысла, это хотелка, которая внесёт кучу неразберихи, не достигнув никакой цели.


        1. eao197 Автор
          11.04.2026 06:22

          Стандарт это требования к компиляторам.

          Т.е. вы хотите сказать, что стандарт нужен только компиляторам, а мне, как разработчику на С++, стандарт не нужен. Правильно я вас понял?

          В вашей идее нет ни грамма смысла, это хотелка, которая внесёт кучу неразберихи, не достигнув никакой цели.

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