I stumbled upon a bug in my code which I traced down to the fact that arguments to std::bind
"...are never passed by reference unless wrapped in std::ref
or std::cref
".
I have a function template which looks like this (stripped of irrelevant bits):
template<typename F, typename ... Args>
auto eval(F&& _f, Args&&... _args)
{
using ret_t = typename std::result_of<F&&(Args&&...)>::type;
std::function<ret_t()> func(std::bind(std::forward<F>(_f), std::forward<Args>(_args)...)));
// Etc.
}
So far so good, but this breaks down if function _f
takes references as arguments because the arguments are copied or moved unless wrapped in std::ref
or std::cref
.
A way to pass arguments as references to _f
while preserving perfect forwarding as much as possible. I would very much like to avoid having to pass each of the _args
by wrapping it in std::ref
at the origin. Rather, I would like the references to be figured out automagically inside eval()
.
Using std::ref
instead of std::forward
when bind
ing _args
seems to work:
template<typename F, typename ... Args>
auto eval(F&& _f, Args&&... _args)
{
using ret_t = typename std::result_of<F&&(Args&&...)>::type;
// Replace std::forward<Args>() with std::ref()
std::function<ret_t()> func(std::bind(std::forward<F>(_f), std::ref(_args)...)));
// Etc.
}
But I don't know how this behaves with rvalue references. Is it safe to always use std::ref
? Is it an overkill? Do I still have perfect forwarding? Questions abound.
I have also considered replacing std::bind
with a generic lambda, but I am not sure how to capture the arguments in order to achieve perfect forwarding.
Any insight would be appreciated.
Is it safe to always use
std::ref
?
It's as safe as using references normally. So in this case, if func
does not outlive the function eval()
, then it's safe. Even if I pass in rvalues, the references won't dangle. But if you need to store func
somewhere, then this is a recipe for dangling references.
What you would want to do is conditionally wrap them as references. One way would be to provide two overloads for lvalues and rvalues:
template <class T> std::reference_wrapper<T> maybe_wrap(T& val) { return std::ref(val); }
template <class T> T&& maybe_wrap(T&& val) { return std::forward<T>(val); }
lvalues turn into reference wrappers, rvalues stay as rvalue references. Now:
std::function<ret_t()> func(std::bind(std::forward<F>(_f),
maybe_wrap(std::forward<Args>(_args))...));
is safe.
Note, the way the bind expression will be invoked and the way you're determining the return type don't quite match. All the bound arguments are passed as lvalues, so what you really need is:
using ret_t = typename std::result_of<F&(Args&...)>::type;
// ^^^^^^^^^^^
You can also replace this with a lambda, which is awkward because of the limited ways to capture parameter packs. You'd have to go through a tuple and implement std::apply()
(which is doable in C++14):
[f=std::forward<F>(f),
args=std::make_tuple(std::forward<Args>(_args)...)]() -> decltype(auto)
{
return std::apply(f, args);
}
Though, due to the necessary hurdles and the fact that this is going to be type erased anyway, maybe bind()
is better.