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;
});
}
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;
});
}
Share
Improve this question
asked Jan 29 at 8:52
TirafesiTirafesi
1,4793 gold badges19 silver badges39 bronze badges
1
- stackoverflow/a/34731847/4688321 - there is a solution given here. Check it out. – kiner_shah Commented Jan 29 at 9:04
2 Answers
Reset to default 8Naive solution
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
.
Improved solution
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;
});
}
Alternative solution
Yet another solution can be seen at https://stackoverflow/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.
In the following code the lambda captures the function and its arguments by value, ensuring that they are correctly forwarded. The use of mutable allows modification of captured variables if necessary. This approach effectively removes the need for the wrapThreadHelper function while maintaining the intended behavior
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(f, std::forward<Args>(args)...);
cout << "cleaning the thread..." << endl;
});
}
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;
}