Search code examples
c++c++14std-function

Creating std::function with a member function does not compile


I am facing compilation issues with a C++ class.

My code is here:

#include <chrono>
#include <cstdint>
#include <thread>
#include <functional>

class Monitors {

public:

    Monitors(std::uint32_t timeout1, std::uint32_t timeout2) : timeout_1(timeout1), timeout_2(timeout2) {
        timer_start(task_1, timeout1); //does not compile
        timer_start(&Monitors::task_2, timeout2); //does not compile either

    }

private:

    void timer_start( std::function<void(void)>& func, std::uint32_t interval) {
        std::thread([func, interval]() {
            while (true) {
                func();
                std::this_thread::sleep_for(std::chrono::milliseconds(interval));                }
        }).detach();
    }

    void task_1() {
    }

    void task_2() {
    }


private:

    std::uint32_t timeout_1;
    std::uint32_t timeout_2;
};

The error is:

non const lvalue to type std::function cannot bind to a temporary of type void

What is the problem? How can I fix it?


Solution

  • The compiler needs to convert the member function to a std::function<void()> object to match the timer_start function argument list.

    This object is a temporary object, whose lifetime will end once the call to timer_start is done. And temporary objects can't be bound to non-constant references (this is what the error message is saying).

    The simple solution is to pass the function by value instead.

    void timer_start( std::function<void(void)> func, uint32_t interval)
    

    A second problem is that for member function you always need to use the address-of operator to get a pointer to it. And you always need to specify the class as well.

    Which means you need to do

    timer_start(&Monitors::task_1, timeout1);
    timer_start(&Monitors::task_2, timeout2);
    

    Finally a third problem: Non-static member functions are special, in that they need an object to be called on. This object is what becomes the this pointer inside the function.

    The object that the function is called on is normally passed as a hidden first argument, but when using e.g. std::function we need to specify that argument explicitly.

    So the final declaration needs to be

    void timer_start( std::function<void(Monitors*)> func, uint32_t interval)
    

    This last thing also means that you need to pass a pointer to an object to the function, in your case you can pass this:

    // Capture this
    //           vvvv
    std::thread([this, func, interval]() {
        while (true) {
            auto x = std::chrono::steady_clock::now() + std::chrono::milliseconds(interval);
            func(this);
            //   ^^^^
            // Pass this as argument
            std::this_thread::sleep_until(x);
        }
    }).detach();