Apparently, throwing and catching std::optional<std::exception>::value()
cannot be handled polymorphically. First if we consider
try
{
throw std::logic_error("blah ..");
}
catch(const std::exception& e)
{
std::cout << "E: " << e.what() << std::endl; // OK, outputs "blah .."
}
We get get the expect output, but if we try
std::optional<std::exception> error = std::logic_error("Booo ..");
try
{
throw error.value();
}
catch(const std::exception& e)
{
std::cout << "E: " << e.what() << std::endl; // NOPE, outputs "std::exception"
}
The exception object got sliced.
I understand that this is because I'm actually throwing optional's template type, which is std::exception
and not std::logic_error
. That is, we basically changed the throw
expression in the first example to
std::exception err = std::logic_error("Blah ..");
throw err;
which gives the same behavior.
So how can I make my std::optional<std::exception>::value()
return a derived exception? Does it have to contain a pointer?
If you want to pass exceptions around in a value type that may or may not hold them, along with type erasure of the exception type, you turned to the wrong tool from the standard library. That's what std::exception_ptr
is for: it exists to pass a thrown (or ready to to be thrown) exception handle around.
auto error = std::make_exception_ptr(std::logic_error("blah .."));
// stuff
try {
std::rethrow_exception(std::move(error));
} catch (std::exception const& e) {
std::cout << "E: " << e.what() << std::endl;
}
Here it is live on godbolt.
std::exception_ptr
is how the standard library bridges the gap between library code that needs to move exceptions around points of execution, and the core exception mechanism. Due to this, it isn't limited to just exceptions derived from std::exception
, as we can throw arbitrary types in C++.