A complex "calculator" is currently invoked like this:
result = calculator.calc(a, b, extra);
Unfortunately, it may sometimes fail due to unrelated changes happening on the server. Synchronizing with different departments is difficult -- retrying is easier...
To that end, I implemented a retry
lambda-function:
auto retry = [](const auto& func, auto... args) {
int attempts = 5;
for (;;) try {
return func(args...);
}
catch (const std::system_error& e) {
/* Don't retry in case of a system_error */
throw;
}
catch (const exception& e) {
if (attempts-- == 0)
throw; /* Give up and rethrow */
Logging::log(Logging::Level::Warning,
"%s. Retrying %d more times.", e.what(), attempts);
sleep(2);
}
};
The above compiles, but, as I try to use it:
result = retry(calculator.calc, a, b, extra);
I get an error from clang: reference to non-static member function must be called
. GNU c++ flags the same line with invalid use of non-static member function
.
Indeed, the calc()
is a non-static method of the Calculator-class.
How would I make use of my new lambda?
In the lambda, replace func(args...)
with std::invoke(func, args...)
. Now your lambda supports member function pointers as callable, in addition to function pointer/references, lambdas, and other function objects.
To pass the member function pointer, the syntax is:
result = retry(&Calculator::calc, &calculator, a, b, extra);
where Calculator
is the class type of calculator
. The extra argument &calculator
is necessary to invoke the non-static member function on the specified object/instance of the class. std::invoke()
knows how to handle it automatically.
You need to be a bit careful here. Because you pass the arguments by-value, if you write calculator
instead of &calculator
you will accidentally call the member function on a copy of calculator
.
Generally, it would be better to pass the arguments by forwarding reference:
auto retry = [](auto&& func, auto&&... args) {
Then you can also call it as:
result = retry(&Calculator::calc, calculator, a, b, extra);
std::invoke()
knows how to handle both pointers and references to the class instance when invoking member function pointers.
Note however that, because you potentially call func()
multiple times, you shouldn't std::forward
the forwarding references into the call.
Also, as a side note, if you want to keep retry
generic, it is not enough to catch std::exception
. There is no requirement that exceptions must be derived from std::exception
. That's just a convention used in the standard library, and sometimes by other libraries.
A fall-back catch
block for all other exceptions should look like this:
catch(...)
{
// do something
}