Search code examples
c++multithreadinglambdastdmove

Lambda capture with std::move and this


I'm actually working to delete most of my copy constructor/assignation, and I faced an issue when comming to lambda use:

ThreadPool:
using Task = std::function<void()>;

template<size_t S>
void ThreadPool<S>::enqueue(Task _task)
{
    {
        std::unique_lock<std::mutex> lock(m_mutex);

        m_queue.push(_task);
    }
    m_cond.notify_one();
}
My actual issue:
class MyClass {
    public:
        MyClass();            // some implementation
        ~MyClass();           // some implementation

        void process(Data &_data)
        {
            if (_data.data == 1)
                m_tp.enqueue([this, data = std::move(_data)] () {
                    func1(data);
                });
            else if (_data.data == 2)
                m_tp.enqueue([this, data = std::move(_data)] () {
                    func2(data);
                });
            else
                m_tp.enqueue([this, data = std::move(_data)] () {
                    func3(data);
                });
        }

        void MyClass::func1(const Data &_data)
        {
            // use of data
        }

        // func2 and func3 have the same signature

    private:
        ThreadPool<4> m_tp{};
};

Data class only declare move constructor/assignation, but when doing so, I got this error:

> /usr/include/c++/11/bits/std_function.h: In instantiation of ‘std::function<_Res(_ArgTypes ...)>::function(_Functor&&) [with _Functor = MyClass::process(Data&)::<lambda()>; _Constraints = void; _Res = void; _ArgTypes = {}]’:
> /usr/include/c++/11/bits/std_function.h:439:69: error: static assertion failed: std::function target must be copy-constructible
>  439 |           static_assert(is_copy_constructible<__decay_t<_Functor>>::value,
> /usr/include/c++/11/bits/std_function.h:439:69: note: ‘std::integral_constant<bool, false>::value’ evaluates to false
> ...

But I can't figure out, how to fix this compilation error and keep the std::move(_data) in lambda capture. I tried using mutable but it just give me the same error if I use it on all of the lambda and some similar error if I just use it on one.


Solution

  • The lambda is always going to be non-copyable because its closure type must have a Data member (which is intentionally non-copyable).

    However, std::function by design requires all callable that it stores to be copyable so that the std::function object itself can also be copied with value semantics (i.e. the stored callable is actually copied, two std::function instances don't reference the same callable object).

    So you can't store such a lambda in a std::function.

    In C++23 there is std::move_only_function which can't be copied and so doesn't require the stored callable to be copyable either. It also improves on some other issues with std::function's design. In C++26 there will also std::copyable_function which is again copyable by virtue of requiring stored callables to be copyable while also making std::move_only_function's other design improvements over std::function.

    If you can't use std::move_only_function, then you'll have to implement analogues move-only type erasure manually in order to store the lambda as a class member. Although, for just the call to enqueue, it would be sufficient to replace Task with an unconstrained template parameter to accept the lambda directly.