Search code examples
c++exceptionc++17stdoptional

Can I make std::optional<std::exception>::value() return the (polymorphic) inherited exception, and not std::exception?


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?


Solution

  • 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++.