LINUX.ORG.RU

Функциональщина на C++

 , ,


0

5

По мотивам: Си с классами

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

Как эффективно учиться? (комментарий)

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

Конкретно мне интересен функционально-процедурный подход к написанию кода на крестах, что-то похожее на Rust, только без абсурдной помешанности на безопасности памяти, так сказать «си с плюшками», но совсем НЕ «си с классами», как было в упомянутом треде. Для примера: Qt и UE — это примеры плохой архитектуры в данном контексте. Например, fstream — это плохая реализация файловых операций, поскольку скатывается в классы и исключения, в ней даже нельзя без исключений получить конкретную ошибку файловых операций.

Итак: какие есть конкретные хорошо проработанные приемы и библиотеки для писания на крестах в функционально-процедурном стиле?

★★★

Одна из первых проблем, с которой сталкивается желающий писать на «си с плюшками» — это возврат ошибок при инициализации обЪекта/структуры:

https://250bpm.com/blog:4/ — Why should I have written ZeroMQ in C, not C++ (part I) (Exceptions + constructors)

Здесь, я бы сказал, неосилятор крестов жалуется на то, какие кресты плохие. Да, они плохие, но не настолько, чтобы говорить «кресты не нужны, переписываем всё на Си».

Какие у нас есть варианты для инициализации с ошибкой? Например, сделать конструктор private и вынести создание в отдельный метод. Возмем упомянутую кривую архитектуру fstream и завернем ее в примитивную функциональную прокладку:

#include <variant>
#include <fstream>

class MyClass {
  private:
    MyClass() { } // empty
  public:
    std::ifstream f;
    template<typename T>
    static std::variant<MyClass, const char *> create(T fileName) {
        auto newInstance = MyClass();
        f.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try {
            f.open(fileName);
        } catch (std::system_error& e) {
            return std::variant(e.code().message());
        }
        return newInstance;
    }
};

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

auto instance = MyClass;
if (std::holds_alternative<MyClass>(instance))
    std::get<MyClass>(instance);

Есть ли что-то проще и чище? std::optional<MyClass> create(T fileName, std::error_code& ec) ?

byko3y ★★★ ()

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

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

Мне кажется, или ты ССЗБ?

intelfx ★★★★★ ()
Последнее исправление: intelfx (всего исправлений: 1)
Ответ на: комментарий от intelfx

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

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

anonymous ()
Ответ на: комментарий от anonymous

Исключения сделаны так, что неосновная ветка явно не обрабатывается и основной код «как бы» превращается в линейный, без if’ов и прочих pattern-matching’ов

Вот именно: как бы не обрабатывается, как бы не тестируется, и как бы непонятно как работает. Потому в ядре линукс C++ в ближайшие лет 10 точно не будет.

byko3y ★★★ ()

а собственно к чему эти поедания кактуса? зарядка для хвоста, или попытка доказать, что ты один функциональный д’Артаньян, а остальные …?

весь С++ в первую очередь про ООП, просто синтаксис крестов не исключает возможности писать в функциональном стиле, иначе ты обречен на полный отказ от стандартной библиотеки

EugeneBas ★★ ()
Ответ на: комментарий от intelfx

Ты берёшь язык с исключениями, со стандартной библиотекой построенной вокруг исключений, внимательно на это всё смотришь, морщишь носик и восклицаешь «фу, исключения»

Это не совсем так. В стандартной либе, особенно в последнее время, возникают функции noexcept, которые таки возвращают ошибку: https://en.cppreference.com/w/cpp/filesystem/create_directory
https://en.cppreference.com/w/cpp/filesystem/copy

byko3y ★★★ ()
Ответ на: комментарий от anonymous

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

Что делать, если паника недопустима? Для многих системных софтин это актуально.

byko3y ★★★ ()
Ответ на: комментарий от EugeneBas

а собственно к чему эти поедания кактуса? зарядка для хвоста, или попытка доказать, что ты один функциональный д’Артаньян, а остальные …?

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

весь С++ в первую очередь про ООП, просто синтаксис крестов не исключает возможности писать в функциональном стиле, иначе ты обречен на полный отказ от стандартной библиотеки

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

byko3y ★★★ ()

Итак: какие есть конкретные хорошо проработанные приемы и библиотеки для писания на крестах в функционально-процедурном стиле?

Так в процедурном или функциональном стиле? Как бе практически противоположные вещи.

По ФП можно почитать в свежей книжке Ивана Чукича «Функциональное программирование на языке C++». Легко гуглится pdf на русском. Про популярные библиотеки особо не в курсе. Школьные поделки на гитхабе, пытающиеся в различной степени имитировать хаскель, не в счет. А стандартная библиотека С++ 20 еще толком нигде не используется. Через несколько лет может быть что-нибудь толковое и появится.

archie ()
Ответ на: комментарий от byko3y

как бы не обрабатывается

Это хорошо или плохо, что код «как бы» линейный - без ветвлений и прыжков неизвестно куда? Еще раз: чего хочется? И этот за выбором последует соответствующая ответственность.

Хочется do-нотаций, но не хочется знать про монады? И чем это отличается от нежелания знать, что происходит в неосновной ветке? «Сложность» (колмогоровская) все равно вылезет откуда не ждали.

Потому в ядре линукс C++ в ближайшие лет 10 точно не будет.

И о чем это говорит? Как будто rust однозначный, непаникующий и на белом коне (как негр из олдспайса)? Но пихают же. Это уже больше «политическое» решение, чем архитектурное.

anonymous ()
Ответ на: комментарий от byko3y

Есть ли что-то проще и чище? std::optional<MyClass> create(T fileName, std::error_code& ec) ?

Можно сделать шаблонный класс наподобие Result<T, E> из раста и писать код типа:

static Result<std::unique_ptr<MyClass>, std::string> create(const T &fileName)

Для того, чтобы избежать геморроя с обработкой возвращаемого результата, можно понаделать всяких методов типа map или and_then, опять же как в расте. Или использовать библиотеки вроде такой. Но без паттерн матчинга и ду нотации ты далеко не уедешь. Имхо С++ не шибко подходит для функционального программирования. Попытки писать на нем в ФП стиле быстро превращаются в нечитабельную дрисню, кратно хуже аналогичного ООП кода с исключениями или, прости г-ди, кодами ошибок.

archie ()
Ответ на: комментарий от archie

Так в процедурном или функциональном стиле? Как бе практически противоположные вещи

Ты про ML/F# слышал когда-нибудь? Между прочим, в C# очень интенсивно идет переход именно в функционально-процедурном направлении.

По ФП можно почитать в свежей книжке Ивана Чукича «Функциональное программирование на языке C++»

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

А стандартная библиотека С++ 20 еще толком нигде не используется

Разве в C++20 что-то особо значимое появилось? Концепты, корутины, модули — без них вполне живут.

byko3y ★★★ ()
Ответ на: комментарий от anonymous

Это хорошо или плохо, что код «как бы» линейный - без ветвлений и прыжков неизвестно куда?

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

Хочется do-нотаций, но не хочется знать про монады?

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

Как будто rust однозначный, непаникующий и на белом коне (как негр из олдспайса)? Но пихают же. Это уже больше «политическое» решение, чем архитектурное

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

byko3y ★★★ ()
Ответ на: комментарий от archie

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

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

Но без паттерн матчинга и ду нотации ты далеко не уедешь

Да, вычислительные выражения и ду-нотации (например, на базе Maybe) по своему назначению частично пересекаются с исключениями — иначе придется явно городить кучу вложенных блоков обработчиков. Собственно, тех самых блоков из Си, где иногда подменяют структурированный код из этих блоков на goto или return, что получается визуально проще, но ни разу не проще для понимания и поддержки.

byko3y ★★★ ()
Последнее исправление: byko3y (всего исправлений: 1)
Ответ на: комментарий от anonymous

Монада, как эндофунктор

Что еще «нового и интересного» расскажешь?

Не эндофунктор разве? Мутки с апликативными функторами и простыми функторами как раз были придуманы для того, чтобы отличать их от эндофунктора-монады. Хотя для простого программиста эти сущности одинаковы. Простого — это который просто хочет, чтобы код работал, и не хочет только мастурбировать без конца над обдумыванием функторов с самыми сексапильными сочетаниями категорий.

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

Если ты посмотришь на «крупные» проекты на хаскеле, то заметишь, что в большинстве своем они предпочитают с этими сущностями дела не иметь, то есть, не выполнять полезной работы: pandoc неинтерактивно преобразовывает файлы по однажды заданной команде; примерно тем же занимается GHC, за исключением GHCi, который на самом деле не сильно далеко вперед ушел и отличается лишь возможностью задавать новые команды без перезапуска; Darcs, который опять же обрабатывает одиночные команды. И так далее, я пока что не слышал про исключения из правила.

byko3y ★★★ ()
Последнее исправление: byko3y (всего исправлений: 1)
Ответ на: комментарий от anonymous

это путь питонистов, это у них постоянно софт и библиотеки падают на каждый чих пых с исключениями.

у ТСа какое-то садо мазо и исключения в конструкторе и отказ от стандартного try/catch на создание этого класса.

bhfq ★★★★★ ()

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

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

safocl ★★ ()

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

safocl ★★ ()

Монад нет, тайпклассов нет, нормальной TCO нет.

Если нужна скорость - бери C++ с OOP, шаблонами и элементами FP. Если не нужна - не бери C++. Пожалей коллег и сроки проекта, иначе они тебя настигнут.

anonymous ()
Ответ на: комментарий от safocl

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

Отвечал уже — делали, но не для fstream:

Функциональщина на C++ (комментарий)

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Ты про ML/F# слышал когда-нибудь?

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

Между прочим, в C# очень интенсивно идет переход именно в функционально-процедурном направлении.

Это кстати общий тренд. Мейнстримные языки потихоньку растаскивают различные фичи из функциональных. Сами ФП языки вряд ли когда-нибудь станут популярными. Скорее мейнстримные мутируют в некое их подобие.

Разве в C++20 что-то особо значимое появилось? Концепты, корутины, модули — без них вполне живут.

Ну вот рейнджи появились с пайпами |, вот это все. Время в крестоболоте конечно течет медленно, но рано или поздно после широкого распространения С++ 20 эти фичи начнут потихоньку использовать, популяризовывать и пилить библиотеки под них. Ну а пока что слишком рано про них говорить, подозреваю что большинство еще С++ 14/17 толком не осилили.

archie ()
Ответ на: комментарий от safocl

по моему это вообще неимоверно люто удобная и гибкая сущность — где понадобилось, там и обработал

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

byko3y ★★★ ()
Ответ на: комментарий от safocl

ага — это все вроде из-за давления гугла?

Ну как бы это всегда подразумевается, потому что гугл возглавляет комитет и является основным потребителем крестов на рынке. Это как раньше был AT&T, так теперь гугл.

byko3y ★★★ ()
Ответ на: комментарий от safocl

а чем плохи исключения в конструкторе — или ты еретик не юзаешь смартпоинтеры?

Пасаны, успокойтесь, это не конструктор — это статический метод, возвращающий новый объект.

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Вот именно это мне и интересно: в какой момент уклон в ФП стиль превращает кресты в дристню.

Приблизительно сразу же, как пытаешься этот самый ФП стиль использовать. Ну попробуй например эмулировать алгебраические типы данных через кучку структурок, засунутых в std::variant. Напиши несколько функций для работы с ними, которые возвращают тип Result<T, E> как я писал выше. Объедини их в цепочку через кучу вложенных блоков обработчиков (аля ехала лямбда через лямбду). Мапни все это какими-нибудь алгоритмами навроде std::transform вместо отсутствующих list comprehensions. Не забудь каждую переменную обмазать const и вообще отказаться от оператора присваивания. А потом сравни полученное с идиоматичным ООП/императивным кодом.

archie ()
Ответ на: комментарий от safocl

Потому что не понятно глядя на код намеренно вот здесь проигнорено исключение или потому что забыто. Потому что в явном виде не видны ветвления пути исполнения. Да и чревато копипастой или переизобретением колеса при обработке ошибок уровнем выше(допустим у нас код с исключением вызывается в 2-3 местах)

cobold ★★★ ()
Ответ на: комментарий от archie

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

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

Это кстати общий тренд. Мейнстримные языки потихоньку растаскивают различные фичи из функциональных. Сами ФП языки вряд ли когда-нибудь станут популярными. Скорее мейнстримные мутируют в некое их подобие

Я вот только не понимаю, откуда у этого тренда ноги растут. Так-то я вижу до сих пор, что все-таки несмотря на этот тренд стиль класс-ориентированного программирования остается мейнстримом. Собственно, императивные языки еще давным-давно позаимствовали у функциональных рекурсивные вызовы и локальные данные, которых не было у Фортрана и Кобола, так что тенденция не нова — все языки друг у друга что-то заимствуют. Но тренд в C# прям очень заметный, даже заметнее аналогичного крестового.

Разве в C++20 что-то особо значимое появилось? Концепты, корутины, модули — без них вполне живут.

Ну вот рейнджи появились с пайпами |, вот это все

Ренжи были довольно давно, пайпов не было, да.

Ну а пока что слишком рано про них говорить, подозреваю что большинство еще С++ 14/17 толком не осилили

Можно посмотреть статистику — действительно, не осилили.

byko3y ★★★ ()
Ответ на: комментарий от archie

Ну попробуй например эмулировать алгебраические типы данных через кучку структурок, засунутых в std::variant. Напиши несколько функций для работы с ними, которые возвращают тип Result<T, E> как я писал выше. Объедини их в цепочку через кучу вложенных блоков обработчиков (аля ехала лямбда через лямбду). Мапни все это какими-нибудь алгоритмами навроде std::transform вместо отсутствующих list comprehensions. Не забудь каждую переменную обмазать const и вообще отказаться от оператора присваивания

Замечание справедливое, но проблема главным образом заключается в том, что вместо какого-нибудь /i = i % 2 == 0 приходится писать [](int i) { return i % 2 == 0; }, аналогично объявления ADT на базе std::variant многословнее по сравнению со встроенным ADT и паттерн матчингом (которые есть в C#, к слову). Если придумать какие-то более короткие конструкции — можно жить чуток получше. ХЗ что, может даже на макросах сишных что-то сварганить.

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Справедливости ради, в расте нет подобного уродства.

match MyClass::create() {
    Ok(instance) => /* ... */,
    Err(err) => /* ... */
}

Или если на ошибку плевать, то:

if let Ok(instance) = MyClass::create() {
    /* ... */
}

Потому все так и молятся на sum types и pattern matching - что бы грязи как в твоём комменте не видеть.

anonymous-angler ()
Последнее исправление: anonymous-angler (всего исправлений: 1)

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

C++ сильно отстает в плане тулинга: нет интегрированного с компилятором сборщика проектов с динамическим графом зависимостей, нет своего пакетного менеджера, невозможно делать рефлексию и вот это вот всё, что сейчас нужно для программирования в большом.

Это всё в значительной степени лечится, и я, например, в своих проектах использую Clang-based кодогенераторы для расшивки узких мест TMP и автоматизации бойлерплейта. Для современного эффективного С++ это просто необходимость, и тебе это всё придется делать самому. Я не поленился, потратил полгода на изучение кодовой базы Clang и теперь могу делать себе такие инструменты, которые мне нужны.

К сожалению, сейчас это данность, что нам приходится самим костылять вот такую инструментальную обвязку для свои проектов. Тогда как те же растоманы могут пользоваться макросами. И, слава ЖГ, что у нас есть Clang для этого. Потому что тот же Столлман до сих пор сопротивляется как может экспорту AST из GCC для внешних инструментов (информация может быть устаревшей, но не суть).

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

Теперь касательно твоего вопроса:

Итак: какие есть конкретные хорошо проработанные приемы и библиотеки для писания на крестах в функционально-процедурном стиле?

У меня есть Memoria. В данном контексте она дает функциональные/персистентные структуры данных, которые уже могут использоваться в функциональном стиле программирования для распараллеливания. Только они не для heap, а для внешней памяти и больших данных.

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

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

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

aist1 ★★ ()

fstream — это плохая реализация файловых операций

Зато быстрая.

std::cout быстрее fmt::print, std::printf, Rust print! и т.д.

https://gist.github.com/fsb4000/7618e7ca2646cc1ef785d8ccf73e0858

https://imgur.com/a/B0BI3tp

Но многим нравится fmt::print, хоть он и в 3 раза медленнее чем std::cout

Грусть, печаль :(

fsb4000 ★★★★★ ()
Ответ на: комментарий от fsb4000

Грусть, печаль :(

Как инженер по данным я тебе скажу, что там, где действительно очень важна скорость вывода, не будет ни printf, ни cout, ни, тем более, fmt::print. Там будет что-то на AVX, да еще и мультипоточное. Это если по каким-то причинам на выходе нужен текст. Иначе будет бинарный формат а-ля Apache Parquet.

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

aist1 ★★ ()
Ответ на: комментарий от byko3y

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

с кодами ошибок симметрично тут...

safocl ★★ ()
Ответ на: комментарий от cobold

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

safocl ★★ ()
Ответ на: комментарий от fsb4000

Тогда знаешь, что нет исключений…

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

safocl ★★ ()