Pull to refresh

Comments 122

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Нет.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Простейшая ситация: класс, конструктор которого получает 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 все-таки отрицательный. Конструктор бросает исключение. Это исключение где-то выше ловится.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Более того, это настолько хорошо зарекомендовало себя на практике, что в 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

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

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

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

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

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

насколько я знаю, 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.

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

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

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

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

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

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

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

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

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

А еще лучше - используйте систему типов для предотвращения возможности таких ошибок, например 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 тут функции, возвращающие минимально/максимально возможные значения переменной.

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

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

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

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

На одной из моих работ мы искали самый удобный вариант реализации обработки ошибок. Долго обсуждали, тестировали варианты, пришли к некому решению. Наше решение было такое: всем методы, которые по логике должны вернуть результат типа 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); // вызываемко конструктор без валидации внутри
}

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

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

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

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

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

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

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

Иииии…?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

D это dlang

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

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

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

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

В целом, да.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Я так понимаю, что логика комитета состоит в том, что старый код должен компилироваться новыми стандартами если в этом коде нет того, чтобы было явным образом удалено из языка. Грубо говоря, если был код, написанный в С++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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Мне это очень напоминает утечку памяти - все больше от С++ вытекают другие языки призванные его "решить" или даже "убить" - тот же Rust, TrapC, Zig, Carbon и другие. И именно из-за такого большого наплавления других языков начинается путаница - вроде все от С++, а каждый не хочет дружить с каждым (ну или я плохо знаю о них,подправьте если что)

И про отдельную стадию с реализацией - вообще хорошая идея, были случаи когда вводили в стандарт, а потом "упс, а оно не работает то оказывается! ¯\_(ツ)_/¯" (вспоминаем тот же плачевный std::auto_ptr)

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

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

P.S. Подумал еще об стандартах - на самом деле палка с двумя концами, которую никто не хочет брать. Вот сколько не замечал - все крупные проекты сидят на одних стандартах, никто не хочет обновляться. А если делают - то только в совсем безумных обновлениях, когда прям уверены что могут. И в тоже время комитет С++ "нужно сделать фичу так, чтобы она запустилась из кода С++98" - ну классно просто. И программисты не идут в будущее, и комитет не идет в будущее. Пути эволюции свернули куда-то не туда...

были случаи когда вводили в стандарт, а потом “упс, а оно не работает то оказывается! ¯_(ツ)_/¯” (вспоминаем тот же плачевный std::auto_ptr)

auto_ptr хотя бы в стандарт попал. И им хотя бы было можно пользоваться. В рамках существовавших тогда возможностей.

А вот export template, например, так до пользователей и не дошел. Как и удаленная в итоге из стандарта поддержка GC.

Или, например, std::aligned_storage, который в стандарт сперва включили, а потом исключили. Или std::is_trivial.

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

Но если с++ не загнётся, а преодолеет эти трудности, возможно у нас будет язык, близкий к идеальному. Так скажем граалем всего программисткого комьюнити.

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

С++ стандартов где-то позже 11 или 17 существенно переусложнён и идёт по пути языка Ада - т.е. нормальный программист может держать в голове и постоянно пользоваться ограниченным объемом возможностей языка.

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

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

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

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

--------------

По поводу исключений и обработки ошибок:

  1. Исключения имеют практический смысл в двух случаях: а) в модульных тестах для выхода по панике без падения процесса б) для использования вместо longjump.

  2. Если у вас в коде есть ошибка, которую вы не знаете как обработать - кидать исключение - самая глупая идея, которая только может придти вам в голову. Самое разумное в этом случае: немедленно завершить процесс. Попытка продолжения скорее всего приведёт к худшим последствиям, чем немедленное завершение - например, будет не просто потеря, но искажение данных. Если в какой-то ситуации вы провели анализ и выяснили, что "в данном месте, деление на ноль может случиться при вот таких условия, и в этом случае мы должны сделать вот это" - вам не надо кидать исключение. А если вы не можете понять, "как вот здесь оказалось деление на ноль" - немедленно паникуйте с дампом памяти.

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

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

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

Самое разумное в этом случае: немедленно завершить процесс.

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

Промахнулся кнопкой +1 :(

Так и есть. Не любая ошибка это катастрофа.

Скажем пришел неправильный запрос от клиента это ведь не катастрофа.

Скажем пришел неправильный запрос от клиента это ведь не катастрофа.

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

и идёт по пути языка Ада …

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

Если у вас в коде есть ошибка, которую вы не знаете как обработать - кидать исключение - самая глупая идея, которая только может придти вам в голову. Самое разумное в этом случае: немедленно завершить процесс …

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

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

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

{исключений/memset/memmove/slice/append/pop/{ptrinit/ptrdeinit/namespacebranching}}

и при компиляции я должен видеть ход генерация кода в какую ветку и почему он пошел

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

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

Было бы здорово сделать новый edition, который бы позволял избавится от необходимости иметь header-файлы и позволил бы обращаться к любым декларациям в файле, вне зависимости от очереди определения

Ибо формат модулей уж очень удручает…

Э.. как бы на этом построен весь C++.
Порядок имеет значение.
Добавляем перегрузку и получаем другие инварианты.

#include <string>
#include <type_traits>

// First overload
int f(int a) { return a; }

template<typename T>
concept can_call_f = requires {
    f(T{});
};

void before() {
    static_assert( can_call_f<int>);
    static_assert(!can_call_f<std::string>);

    f(1);
}

// New overload
std::string f(std::string c) { return c; }

template<typename T>
concept can_call_f_2 = requires {
    f(T{});
};

void after() {
    static_assert(can_call_f_2<int>);
    static_assert(can_call_f_2<std::string>);

    f("a");
}

int main()
{
}
Sign up to leave a comment.

Articles