Injecting exception<_> event in subsequent action
For generic exception<_> events, it would be useful to inject the event/exception in the subsequent action. AFAIU, it works for typed exception event such as exception<std::runtime_error> only.
Here is a godbolt that shows the "issue" and a repro:
// $CXX -std=c++14 error_handling.cpp
#include <boost/sml.hpp>
#include <iostream>
#include <stdexcept>
namespace sml = boost::sml;
namespace {
struct exception_handling {
auto operator()() const {
using namespace sml;
return make_transition_table(
*("idle"_s) + "event1"_e / [] { throw std::runtime_error{"error"}; }
, *("exceptions handling"_s) + exception<std::runtime_error> / [] (auto const& evt) { std::cout << "exception caught: " << evt.what() << std::endl; }
);
}
};
} // namespace
int main() {
using namespace sml;
sm<exception_handling> sm;
sm.process_event("event1"_e()); // throws runtime_error
}
Does anyone have a suggestion on how to implement something like this?
First of all, let's recap try-catch-block:
try
{
f();
}
catch (const std::runtime_error& e)
{
log(e.what); // this executes if f() throws std::runtime_error (same type rule) -- here, we do have a what() function
}
catch (const std::exception& e)
{
log(e.what); // this executes if f() throws std::exception (base class rule) -- here, we do have a what() function
}
catch (...)
{
log("catch_all"); // this executes if f() throws int or any other non-exception type -- here, we do NOT have a what() function
}
with this in common
#include "sml.hpp"
#include <cassert>
#include <iostream>
#include <stdexcept>
namespace sml = boost::sml;
namespace {
struct exception_handling {
auto operator()() const {
using namespace sml;
constexpr auto on_exception = [](const auto& evt) {
if (std::is_same_v<std::remove_cvref_t<decltype(evt)>, std::runtime_error>) std::cout << "std::runtime_error: " << evt.what() << std::endl;
else std::cout << evt.what() << std::endl;
};
constexpr auto on_exception_catch_all = [](const auto& evt) {
std::cout << "catch_all" << std::endl;
};
return make_transition_table(
*("idle"_s) + "event1"_e / [] {
//throw std::logic_error{"ouch2"};
//throw std::exception{};
throw std::runtime_error{"ouch"};
}
, *("exceptions handling"_s) + exception<_> / on_exception_catch_all = "fault"_s
, "exceptions handling"_s + exception<std::runtime_error> / on_exception = "fault"_s
, "exceptions handling"_s + exception<std::exception> / on_exception = "fault"_s
);
}
};
} // namespace
int main() {
using namespace sml;
sm<exception_handling> sm;
sm.process_event("event1"_e());
}
In state "exceptions handling" we have to catch all specializations of std::exception on its own, because else the specializations will be casted to std::exception and we no more information about the type.
See Compiler Explorer Example: https://godbolt.org/z/vved46rah
Thanks for looking into this!
Right, we don't have access directly to the exception in a catch all handler, but calling std::current_exception() is an option:
catch (...)
{
std::exception_ptr p = std::current_exception();
}
How about passing an std::exception_ptr to the subsequent action of exception<_>? Or call std::current_exception() in the on_exception_catch_all action?
constexpr auto on_exception_catch_all = [](const auto& evt) {
std::exception_ptr p = std::current_exception();
std::cout <<(p ? p.__cxa_exception_type()->name() : "null") << std::endl;
};
This last option seems to fit the bill for me, but I am not sure it would work with the defer_queue policy.