I believe the type-safe way to design this API has already been designed and used in practice, e.g. in Folly.
auto future = async(...);
future.then([](int result) {
use(result);
}).on_error([](std::exception_ptr exc) {
handle(exc);
});
That is, .then() allows "unwrapped" callbacks, and if you need to handle the exceptional case, then you add an .on_error() handler. The above code is 100% equivalent in semantics to
auto future = async(...);
future.then([](std::future<int> maybe_result) {
try {
int result = maybe_result.get();
use(result);
} catch (...) {
std::exception_ptr exc = std::current_exception();
handle(exc);
}
});
except that the former version is much much faster, because it doesn't have to do any atomic operations and it doesn't have to throw any exceptions on the exceptional path. (I'm fairly sure that throwing that exception is going to be much much slower than the couple of atomic exchanges necessary to lock and unlock the shared state of the future.)
I've also seen "unwrapped .then()" referred to as ".on_value()", which has nice symmetry and also makes the metaprogramming much easier.
–Arthur