Is it possible to write a template function and pass it a list of exception types to handle?
I would like to implement something like this
template<class Exc1, Exc2>
auto foo()
{
try {
bar();
} catch (const Exc1& e) {
log_error(e);
} catch (const Exc2& e) {
log_error(e);
}
}
but have a variable number of exception types to handle.
The following would do what I need, but of course does not compile
template<class... Exceptions>
auto foo()
{
try {
bar();
} catch (const Exceptions& e...) {
log_error(e);
}
}
Why do I need this? I am writing a generic 'retry' mechanism that should call provided callable, retrying several times in case an exception from the list is thrown (but letting all other exceptions bubble up). Currently I bypass the problem by providing two callables: the target function and exception handler:
template<class Function, class ExceptionHandler>
auto retry(Function function, ExceptionHandler handler)
{
for (auto i = 0; i < 3; i) {
try {
return function();
} catch (...) {
handler();
}
}
return function();
}
auto f = [] {
// do something and possibly throw an exception
};
auto h = [] {
try {
throw;
} catch (const Exc1& e) {
log_error(e);
} catch (consy Exc2& e) {
log_error(e);
}
};
retry(f, h);
The above code works as I expect, but I was hoping for a more elegant solution.
CodePudding user response:
you can try to use a recursion
template<class Exception, class... Exceptions>
auto foo()
{
if constexpr (sizeof...(Exceptions) == 0)
{
try {
bar();
} catch (const Exception& e) {
log_error(e);
}
}
else
{
try {
foo<Exceptions...>();
} catch (const Exception& e) {
log_error(e);
}
}
}
CodePudding user response:
If you wish to avoid recursion, and you derive all exceptions from std::exception, you can use a variadic expansion with dynamic_cast
to match the exception type:
#include <stdexcept>
#include <iostream>
template<class...Exceptions, class Handler>
void
try_handle(Handler handler, std::exception& e)
{
bool handled = false;
auto check = [&](auto* pexception)
{
if (!pexception || handled)
return;
handled = true;
handler(*pexception);
};
( check(dynamic_cast<Exceptions*>(&e)), ... );
if (!handled)
std::rethrow_exception(std::current_exception());
}
auto
test(std::exception_ptr ep)
{
auto handler1 = [](auto& ex)
{
std::cout << "handler 1: " << ex.what() << '\n';
};
auto handler2 = [](auto& ex)
{
std::cout << "handler 2: " << ex.what() << '\n';
};
try
{
try
{
std::rethrow_exception(ep);
}
catch(std::exception& e)
{
try_handle<std::invalid_argument, std::logic_error>(handler1, e);
}
}
catch(std::exception& e)
{
try_handle<std::runtime_error, std::logic_error>(handler2, e);
}
}
int main()
{
test(std::make_exception_ptr(std::invalid_argument("argument")));
test(std::make_exception_ptr(std::runtime_error("runtime")));
test(std::make_exception_ptr(std::logic_error("logic")));
}
Expected output:
handler 1: argument
handler 2: runtime
handler 1: logic