use std::expected to handle burn failures?
Opened this issue · 4 comments
It would be useful to handle burn/integration failures in a more modern C++ (but exception-free) way.
This could be done by having the burner return an std::expected
object (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0323r3.pdf, monadic functions: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2505r1.html)
(Public domain implementation here: https://github.com/TartanLlama/expected.)
It works like this:
enum class arithmetic_errc
{
divide_by_zero, // 9 / 0 == ?
not_integer_division, // 5 / 2 == 2.5 (which is not an integer)
integer_divide_overflows, // INT_MIN / -1
};
expected<double, errc> safe_divide(double i, double j)
{
if (j == 0) return unexpected(arithmetic_errc::divide_by_zero); // (1)
else return i / j; // (2)
}
int divide2(int i, int j)
{
auto e = safe_divide(i, j);
if (!e)
switch (e.error().value()) {
case arithmetic_errc::divide_by_zero: return 0;
case arithmetic_errc::not_integer_division: return i / j; // Ignore.
case arithmetic_errc::integer_divide_overflows: return INT_MIN;
// No default! Adding a new enum value causes a compiler warning here,
// forcing an update of the code.
}
return *e;
}
In principle, std::expected
objects can be propagated higher in the call stack, so it could be used thoughout Microphysics, not just in returning to the application code.
What is an example of something this enables that we can't do right now (with the burn status stored in the burn_t)?
What is an example of something this enables that we can't do right now (with the burn status stored in the burn_t)?
It can propagate up the call chain into parts of our code that aren't Microphysics-specific. This could reduce the amount of boilerplate code significantly by using the and_then
extension (https://github.com/TartanLlama/expected?tab=readme-ov-file#expected).
This extension is now part of C++23: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2505r1.html
For example, we could implement hydro retries like this:
tl::expected<image,fail_reason> attempt_hydro_update (const hydro_state& state) {
return burn_strang_split(state)
.and_then(hydro_stage1)
.and_then(hydro_stage2)
.and_then(burn_strang_split)
}
it looks like this is a C++23 feature, right?
it looks like this is a C++23 feature, right?
Yes. You can also copy this (public domain) header file and use it now with C++17: https://github.com/TartanLlama/expected/blob/v1.1.0/include/tl/expected.hpp.