Home > Mobile >  Catching exception types provided via template parameter pack
Catching exception types provided via template parameter pack

Time:12-20

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);
        }
    }
}

a working example on godbolt

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
  • Related