Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass an object's method to a lambda-function?

Tags:

c++

lambda

c++17

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?

like image 627
Mikhail T. Avatar asked Aug 19 '26 00:08

Mikhail T.


2 Answers

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
}
like image 71
user17732522 Avatar answered Aug 21 '26 14:08

user17732522


You might pass directly a lambda:

retry([&](){ return calculator.calc(a, b, extra); });
like image 30
Jarod42 Avatar answered Aug 21 '26 15:08

Jarod42



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!