Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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:
template<size_t S = N_THREAD_TP>
class ThreadPool
{
    public:
        using Task = std::function<void()>;

        template<size_t S>
        void ThreadPool<S>::start()
        {
            if (m_isStarted)
                return;
            for (size_t it = 0; it < S; it++)
                m_thread[it] = std::thread(&ThreadPool<S>::loop, this);
            m_isStarted = true;
        }

        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();
        }

        template<size_t S>
        void ThreadPool<S>::loop()
        {
            Task task;

            while (!m_terminate) {
                {
                    std::unique_lock<std::mutex> lock(m_mutex);

                    m_cond.wait(lock, [this] () {
                        return !empty() || m_terminate;
                    });
                    if (m_terminate)
                        return;
                    task = m_queue.pop_front();
                }
                task();
             }
        }
   
    private:
        bool m_isStarted = false;
        bool m_terminate = false;
        ts::Queue<Task> m_queue{};

        std::array<std::thread, S> m_thread;
        std::mutex m_mutex;
        std::condition_variable m_cond;
};
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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading