0

I have been learning about perfect forwarding and the use of && in function templates (see this previous question of mine) and would like to know if my use of Args&& in StartDetachedThread() below is justified:

#include <thread>

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

// ** Will not compile with this function declaration! **
void MyThreadFunc(CObject&&)
{
}

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

int main()
{
    CObject object;
    StartDetachedThread(MyThreadFunc, object);

    CObject object2;
    StartDetachedThread(MyThreadFunc, std::move(object2));
    return 0;
}

This code simply creates a detached thread, running the supplied function passing to it the supplied arguments.

Hhowever, VS 2017 complains:

'StartDetachedThread': no matching overloaded function found
'void StartDetachedThread(FunctionType,Args &&...)': could not deduce template argument for 'FunctionType'

1) I know that arguments passed to the thread constructor are copied first, then passed by reference to the new thread, so is my attempt to have MyThreadFunc(CObject&&) called when I pass an rvalue reference never going to work?

2) Is there any value in having StartDetachedThread(FunctionType&& func, Args&&... args) - or is the && unnecessary for FunctionType?

3) Is there any value whatsoever in using Args&& when starting a thread like this, or should I always use Args?

Wad
  • 1,454
  • 1
  • 16
  • 33
  • *"I know that arguments passed to the thread constructor are copied first"* - The lambda is copied, but the lambda only holds references to the args since you captured by ref (`[&]`). You should forward the args to `std::thread` constructor, not capture them inside the lambda, this might cause you problem if the call to `func` inside the thread is deferred after the end of `StartDetachedThread`. – Holt Jan 19 '18 at 11:30
  • OK, I tried what you said, the body of `StartDetachedThread()` is now: `thread(func, forward(args)...).detach();`, but this doesn't compile with `StartDetachedThread(MyThreadFunc, std::move(object2));`. Why is this? I also don't know what you mean by "this might cause you problem" - are you saying that the code version that captures the variables will have problems, or the code change that you suggested will have problems? Can you please post this as answer also... – Wad Jan 19 '18 at 12:12

2 Answers2

1

The issue is that which overload of MyThreadFunc is desired is not deducible by the compiler. There are at least two ways to fix it:

  1. Rename one of the function so that it is clearer which one you want.

  2. Use explicit template parameters:

    StartDetachedThread<void (CObject&)>(MyThreadFunc, object);
    StartDetachedThread<void (CObject&&)>(MyThreadFunc, std::move(object2));
    
AProgrammer
  • 51,233
  • 8
  • 91
  • 143
1

The problem in your code has nothing to do with std::thread, it is because MyThreadFunc is ambiguous in this context:

// Which MyThreadFunc should be used?
StartDetachedThread(MyThreadFunc, object);

Regarding your question:

1) I know that arguments passed to the thread constructor are copied first, then passed by reference to the new thread, [...]

In your example, the only copy is the copy of the lambda. The arguments are not copied here, if you want the argument to be copied you should use something like this:

std::thread(std::move(func), std::forward<Args>(args)...).detach();

...where you forward the arguments to std::thread constructor.

This is safer. — Think about what happens if the function StartDetachedThread ends while the thread is still running?

If you use this, you need to explicitly tell the compiler you want to call the reference version for object1 by using std::ref:

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

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

2) Is there any value in having StartDetachedThread(FunctionType&& func, Args&&... args) - or is the && unnecessary for FunctionType?

3) Is there any value whatsoever in using Args&& when starting a thread like this, or should I always use Args?

Using forwarding references allows you to call StartDetachedThread without having to move everything. If you use the above way for constructing a std::thread, then copies will be made for func and args anyway.

Holt
  • 36,600
  • 7
  • 92
  • 139
  • Thanks. **1)** OK, I'm not sure of the problem if `StartDetachedThread` ends while the thread is still running in my original code. Are you saying that because they are not copied to the thread, they are only forwarded onto `func()`, that there is a risk they will possibly be destroyed sometime after they were created? They won't be destroyed when `StartDetachedThread()` ends will they? – Wad Jan 19 '18 at 15:20
  • **2)** I don't see the value in moving `func`; I'm guessing it has type `std::function`, so why we we need to move it? – Wad Jan 19 '18 at 15:21
  • 1
    @Wad 1) In your two calls, you may see no issue because you are passing references to existing objects, but imagine if you call `StartDetachedThread` with a temporary, e.g. `StartDetachedThread(someFunc, Object{})` - The temporary will get destroyed at the end of the full expression, but you have no guarantee that `func` will be done. – Holt Jan 19 '18 at 15:27
  • 1
    @Wad 2) The type of `MyThreadFunc` depends on what you are passing. It certainly is not `std::function<...>` unless you explicitly create one, e.g. `StartDetachedThread(std::function<...>(MyThreadFunc))`. In the above example, the type is `void (CObject&)`, it's a function type. The move is useless in this case, but if you want a generic code, you could get a not-so cheap to copy object. A `std::function` should be moved because it may not be cheap to copy. – Holt Jan 19 '18 at 15:31