Home > Back-end >  Accept and return lambda (lock wrapper)
Accept and return lambda (lock wrapper)

Time:03-18

I want to accept any lambda, so that it can perform some operation safely under the lock_guard and then return anything, but using as shown below throws an error:

#include <iostream>
#include <functional>
#include <mutex>
#include <memory>

class Test {
    public:
        template<typename Ret>
        Ret DoLocked(std::function<Ret(Test&)> func) {
            std::lock_guard<std::mutex> lock(*mtx);
            return func(*this);
        }
    private:
        std::unique_ptr<std::mutex> mtx = std::make_unique<std::mutex>();
};

int main() {
    Test a;
    a.DoLocked([](Test &s) {
        std::cout << "in do locked" << std::endl;
    });

    return 0;
}
[build] /some-path/test.cc:21:6: error: no matching function for call to ‘Test::DoLocked(main()::<lambda(Test&)>)’
[build]    21 |     });
[build]       |      ^
[build] /some-path/test.cc:9:13: note: candidate: ‘template<class Ret> Ret Test::DoLocked(std::function<Ret(Test&)>)’
[build]     9 |         Ret DoLocked(std::function<Ret(Test&)> func) {
[build]       |             ^~~~~~~~
[build] /some-path/test.cc:9:13: note:   template argument deduction/substitution failed:
[build] /some-path/test.cc:21:6: note:   ‘main()::<lambda(Test&)>’ is not derived from ‘std::function<Ret(Test&)>’
[build]    21 |     });```

CodePudding user response:

This is easily solved by getting rid of std::function and making the function parameter a template parameter. That would look like

template<typename Func>
auto DoLocked(Func func) -> decltype(func(*this)) {
    std::lock_guard<std::mutex> lock(*mtx);
    return func(*this);
}

The reason it doesn't work with the std::function is that a lambda expression does not create a std::function. It creates a object of an unnamed class type that has an operator() defined with the body of the lambda expression. Because of this, template deduction fails as it is expecting a std::function but that is not what is being provided.

CodePudding user response:

I want to accept any lambda, so that it can perform some operation safely under the lock_guard

This is completely wrong assumption which will lead to deadlock.

Locks are to protect consistency of some specific data. Those data usually are fields of some single class.

Now critical section should protect this data and must not invoke any arbitrary code. Why? Since arbitrary code can contain other critical sections. This means that you do not have control on order of locks which are acquired. As a result small unrelated change to code will lead to hard to remove dead lock.

  • Related