Reputation: 480
This question relates to this, this and potentially this.
I have the following class, in which the AddFunction
method receives a function and a list of arguments to that function, then spawns an std::thread
calling the passed function with the passed arguments:
class Processes {
public:
Processes() {}
~Processes() {
for (auto &t : threads_) {
t.join();
}
}
template <class Function, typename... Args>
void AddFunction(Function &&func, Args &&... args) {
threads_.emplace_back(std::forward<Function>(func),
std::forward<Args>(args)...);
}
private:
std::vector<std::thread> threads_;
}
This results in a copy for every argument, and compilation will fail if an object is not copyable, because std::thread
requires references to be wrapped in std::ref
in order to guarantee that this object will exceed the thread's lifetime, and will copy it otherwise.
I want to pass objects by reference when specified in the target function signature.
I tried using a lambda:
template <class Function, typename... Args>
void AddFunction(Function &&func, Args &&... args) {
threads_.emplace_back([&]() { func(std::forward<Args>(args)...); });
}
But this results in incorrect behavior, as the lambda captures values by reference before passing them by value, resulting in capture by reference behavior.
How do I implement a function that forwards arguments either as value or reference according to the target function signature?
Example:
void Foo(int a, std::vector<int> const &b) { /* ... */ }
int main() {
Processes procs;
int a = 6;
std::vector<int> b;
procs.AddFunction(
Foo,
a, // Should be passed by value
b // Should be passed by reference (as implemented by std::ref)
);
return 0;
}
Upvotes: 3
Views: 1055
Reputation: 217478
You might change function signature to be less generic:
First some helpers:
template <typename T> struct non_deducible { using type = T; };
template <typename T> using non_deducible_t = typename non_deducible<T>::type;
template <typename T>
auto passed_by(T& t, std::true_type)
{
return std::ref(t);
}
template <typename T>
T&& passed_by(T&& t, std::false_type)
{
return std::forward<T>(t);
}
And then
template <class Ret, typename... Args>
void AddFunction(Ret (*func)(Args...), non_deducible_t<Args>... args) {
threads_.emplace_back(func,
passed_by(std::forward<Args>(args),
std::is_reference<Args>{})...);
}
Upvotes: 2
Reputation: 93304
If you want to go down the lambda route, you can implement some utilities that allow you to capture by "perfect-forward" - this means that rvalues are moved into the closure and lvalues are captured by reference. You can use std::tuple<T>
to either store T
or T&
(my linked article has a cleaner implementation):
template <class Function, typename... Args>
void AddFunction(Function &&func, Args &&... args)
{
threads_.emplace_back([
targs = std::tuple<Args...>{std::forward<Args>(args)...},
tfunc = std::tuple<Function>(func)]() mutable
{
std::apply([&targs](auto&& x_func)
{
std::apply([&x_func](auto&&... x_args)
{
std::forward<Function>(x_func)(
std::forward<Args>(x_args)...
);
}, targs);
}, tfunc);
});
}
Upvotes: 2