2

I asked a previous question about creating a function that specified a function for a thread to run along with its arguments, where I wanted to pass the name of an overloaded function as the function my thread should run, and have the thread pick the appropriate one depending on what type of arguments I pass. For example:

void MyThreadFunc(CObject& obj) {}  // Should be called when passing an lvalue
void MyThreadFunc(CObject&& obj) {} // Should be called when passing an rvalue

The answer was that I should specify the type of the function that my thread should run as a template argument when calling my thread creation function:

template<typename FunctionType, typename ...Args>
void StartDetachedThread(FunctionType func, Args&&... args)
{
    thread([&]()
    {
        func(forward<Args>(args)...);
    }).detach();
}

CObject object;
StartDetachedThread<void (CObject&)>(MyThreadFunc, std::ref(object)); // Calls MyThreadFunc(CObject&)

CObject object2;
StartDetachedThread<void (CObject&&)>(MyThreadFunc, std::move(object2)); // Calls MyThreadFunc(CObject&&)

This I understand. I then took this knowledge into an interruptible thread implementation that I have built up from snippets found on this site, but hit a snag. Here is my trimmed down thread class:

#include <iostream>
#include <thread>
#include <future>

using namespace std;

class interruptible_thread
{
    std::thread m_threadInternal;
public:

    template<typename FunctionType, typename... Args>
    interruptible_thread(FunctionType&& f, Args&&... args) 
    {
        m_threadInternal = std::thread([&]
        (typename std::decay<FunctionType>::type&& f
            , typename std::decay<Args>::type&&... args)
        {
            f(std::forward<Args>(args)...);            /// ***** COMPILER ERROR HERE
        }
            , std::forward<FunctionType>(f)
            , std::forward<Args>(args)...);
    }
    ~interruptible_thread() {}
    interruptible_thread(interruptible_thread&& rhs) { m_threadInternal = std::move(rhs.m_threadInternal); }
};

class CObject {};
void MyThreadFunc(CObject& obj) {}
void MyThreadFunc(CObject&& obj) {}

template<typename FunctionType, typename... Args>
interruptible_thread CreateThread(FunctionType&& f, Args&&... args)
{
    return interruptible_thread(f, args...);
}

I know I cannot specify the template arguments when constructing my interruptible_thread, so wrote CreateThread() to do that for me. However, when I code

CObject o2;
interruptible_thread thr = CreateThread<void (CObject&&)>(MyThreadFunc, std::move(o2));

VS 2017 complains of the line indicated above:

'void (CObject &&)': cannot convert argument 1 from 'CObject' to 'CObject &&'
You cannot bind an lvalue to an rvalue reference

Now, I may have been looking at this for too long, but I don't understand the problem. Can someone please gently explain?

RAM
  • 2,257
  • 2
  • 19
  • 41
Wad
  • 1,454
  • 1
  • 16
  • 33

1 Answers1

1

You forgot to perfect-forward the args inside CreateThread():

template<typename FunctionType, typename... Args>
interruptible_thread CreateThread(FunctionType&& f, Args&&... args)
{
  return interruptible_thread(std::forward<FunctionType>(f), std::forward<Args>(args)...);
}

If you don't do that then args will turn from rvalue- to lvalue references in order to be passed to interruptible_thread, which won't work.

Caution also: avoid using the name CreateThread in Visual Studio, there is already a WinAPI called CreateThread, which can cause conflicts.

rustyx
  • 80,671
  • 25
  • 200
  • 267