I need to wrap std::thread
to do some processing (inside the new thread) before the user function runs.
At the moment I'm achieving this via a helper function, but that's a bit annoying.
How can I convert wrapThreadHelper
into a lambda inside wrapThread
while keeping the perfect forwarding semantics?
#include <functional>
#include <iostream>
#include <string>
#include <thread>
using namespace std;
struct Foo
{
void bar()
{
cout << "bar is running" << endl;
}
};
template <class F, class... Args>
void wrapThreadHelper(F &&f, Args &&...args)
{
cout << "preparing the thread..." << endl;
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
cout << "cleaning the thread..." << endl;
}
template <class F, class... Args>
std::thread wrapThread(F &&f, Args &&...args)
{
return std::thread(&wrapThreadHelper<F, Args...>, std::forward<F>(f), std::forward<Args>(args)...);
}
int main()
{
Foo foo;
std::thread t1 = wrapThread(&Foo::bar, std::ref(foo));
std::thread t2 = wrapThread([] { cout << "lambda is running..."; });
t1.join();
t2.join();
return 0;
}
I would like to delete wrapThreadHelper
and convert wrapThread
into something like this:
template <class F, class... Args>
std::thread wrapThread(F &&f, Args &&...args)
{
return std::thread([]() {
cout << "preparing the thread..." << endl;
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
cout << "cleaning the thread..." << endl;
});
}
The easiest and shortest way is to capture the function parameters:
template <class F, class... Args>
std::thread wrapThread(F &&f, Args &&...args)
{
return std::thread([=]() mutable {
cout << "preparing the thread..." << endl;
std::invoke(std::move(f), std::move(args)...);
cout << "cleaning the thread..." << endl;
});
}
Note that we need to capture by copy =
because by the time the thread invokes the lambda we give it, the function parameters may no longer exist.
This is problematic when the parameters are temporary objects like std::ref(foo)
.
Since the lambda stores unique copies of f
and args
, it should also use std::move
(not std::forward
) in the lambda body to transfer ownership of its captured copies to std::invoke
.
Unfortunately, [=]
would result in unnecessary copying, but we can fix this with generalized captures:
template <class F, class... Args>
std::thread wrapThread(F &&f, Args &&...args)
{
return std::thread([f = std::forward<F>(f), ...args = std::forward<Args>(args)]()
mutable {
cout << "preparing the thread..." << endl;
std::invoke(std::move(f), std::move(args)...);
cout << "cleaning the thread..." << endl;
});
}
Yet another solution can be seen at https://stackoverflow.com/a/34731847/5740428, which would involve letting the std::thread
store the function parameters like std::forward<F>(f)
and giving the lambda corresponding rvalue reference parameters like std::decay_t<F>&&
:
template <class F, class... Args>
std::thread wrapThread(F &&f, Args &&...args)
{
return std::thread([](std::decay_t<F> &&f, std::decay_t<Args> &&...args) {
cout << "preparing the thread..." << endl;
std::invoke(std::move(f), std::move(args)...);
cout << "cleaning the thread..." << endl;
}, std::forward<F>(f), std::forward<Args>(args)...);
}
While this solution is more complicated, it's technically optimal.
Using a capturing lambda results in at least two calls to move constructors (one when creating the lambda, one when moving the lambda into the std::thread
), while this solution avoids one of these calls.
It's up to you whether you actually care or simply assume that move constructors are cheap, in which case the previous solution is best.