Open apolukhin opened 2 years ago
На всякий случай продублирую тут: если вставать на путь кастомизации выкидывания исключения, то логично обработать стандартные варианты E
-- std::error_code
и std::exception_ptr
.
template<class E>
struct on_error_policy {
[[noreturn]] void operator()(E&& e) const {
throw bad_expected_access(std::forward<E>(e));
}
};
template<>
struct on_error_policy<std::error_code> {
[[noreturn]] void operator()(std::error_code ec) const {
throw std::system_error(ec);
}
};
template<>
struct on_error_policy<std::exception_ptr> {
[[noreturn]] void operator()(std::exception_ptr e) const {
std::rethrow_exception(std::move(e));
}
};
template<class T, class E, class C = on_error_policy<E>>
class expected;
С одной стороны, поддерживаю. Хотелось бы иметь возможность кастомизации.
Попробую ещё один простой пример вкинуть, хотя это частный случай unify local and centralized error handling, на который ссылались на Хабре.
Нередко исключения ловятся в одном единственном блоке catch
как const std::exception&
, в котором просто логируется сообщение об ошибке. Рассмотрим на примере std::filesystem::file_size:
try
{
const auto size = fs::file_size("C:\\file.txt");
if (size > 100)
{
// code ...
}
else
{
// code ...
}
}
catch (const std::exception& ex)
{
std::cerr << ex.what();
}
И если fs::file_size
выбросит исключение, то в логах будет актуальная информация (например: "file does not exist: C:\file.txt").
Допустим, что fs::file_size
теперь возвращает std::expected<std::size_t, std::error_code>
. Если мы просто исправим обращение к size
на size.value()
, то в случае ошибки потеряем в логах информацию о её причинах. Там будет записано что-то вроде "bad expected access".
Тогда нам нужно будет либо добавить ещё один блок catch
:
catch (const std::bad_expected_access<std::error_code>& ex)
{
std::cerr << ex.error().message();
}
Либо обрабатывать ошибку по месту:
const auto size = fs::file_size("file.txt");
if (!size)
{
std::cerr << size.error().message();
return;
}
А если в функции несколько разных std::expected
встретятся, то это вообще жуть будет :)
Поэтому хочется, чтобы при обращении к value()
вылетало "нормальное" исключение.
С другой стороны, как бы эти кастомизации злую шутку не сыграли в конечном итоге, если каждый будет свои policy пилить...
Может, с целью минимизации энтропии попробовать вместо одного std::expected
рассмотреть три? :D
Названия условные:
// Версия expected, что мы здесь обсуждаем
template<class T, class E, class C>
class customized_expected;
// Версия expected, что предлагается в стандарт - продвинутый optional
template<class T, class E>
using value_or_error = customized_expected<T, E, throw_bad_value_access>
// Версия, что изначально предлагал Александреску. Можно даже добавить дополнительное ограничение в виде `std::derived_from<E, std::exception>`
template<class T, class E>
using expected = customized_expected<T, E, throw_exception>
Те кто хочет легковесный продвинутый optional
, будут использовать value_or_error
. Тот кто хочет исключения - expected
.
Да и пользователям, думаю, будет чуть понятнее, какие исключения в каком случае ловить...
Поэтому хочется, чтобы при обращении к
value()
вылетало "нормальное" исключение.
Мне кажется идея std::expected была как раз в том, чтобы не бросать исключение. Ну то есть если у тебя есть некая операция, возвращающая либо значение, либо исключение, то ты уже и так можешь сделать блок catch и забирать просто по значению внутри. Зачем делать промежуточное звено в виде expected в такой ситуации? Это имеет смысл разве что в многопоточной системе и какой то фьюче. А как некая вещь объединяющая error code и значение вроде бы неплохо получилось, использование предполагается типа auto v = Foo(); if (v) do(*v); Как собственно это с еррор кодом происходит, но его неудобно передавать, создавать и можно забыть обработать, в отличие от std::expected
Нужна возможность кастомизации выкидывания исключения. Обсуждение доступно по ссылке https://habr.com/ru/company/yandex/blog/649497/#comment_24051655
В идеале, нужна кастомизация, позволяющая превращать коды ошибок в исключения. Черновой интерфейс: