2

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".

What I have

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.

What I would like

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().

What I have tried

Using std::ref instead of std::forward when binding _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.

cantordust
  • 1,542
  • 13
  • 17
  • 1
    If arg is an rvalue, then you'll have problems, if you take a reference, and pass `func` outside of `eval`. So, you'd like to use `std::ref(std::forward(_args))` instead. This will make the compiler fail, if arg is an rvalue. Other than that, I think this solution is fine (If you don't pass `func` outside to the function, then you are fine with your current solution) – geza Oct 04 '17 at 12:38
  • Thank you, this is very useful. Do you have an idea about what I could do if I do have to pass `func` outside `eval()`? – cantordust Oct 04 '17 at 12:48
  • Then you need to apply copy: don't use `std::ref` in this case. I think this can be done with some template specialization. – geza Oct 04 '17 at 14:01
  • Or with just function overloading: create a `std::ref` like function, which works with rvalue references too (in the case of lvalue reference, it should return `std::reference_wrapper`, in the case of rvalue reference, it should return rvalue reference). – geza Oct 04 '17 at 14:07

1 Answers1

2

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Fantastic answer, thank you! The lambda option is very interesting, but would it be possible to pass it outside `eval()` as a `std::function`? Thank you also for pointing out the correct way to determine the return type. – cantordust Oct 04 '17 at 13:06
  • @cantordust The lambda owns everything (just as your initial implementation using `bind` owns everything), so both are safe. It's when you start holding onto references that have to start thinking carefully. – Barry Oct 04 '17 at 13:44
  • I integrated the `bind()` solution into my program and it works very nicely, thank you. I will try the lambda version as well because it seems more straightforward. – cantordust Oct 04 '17 at 21:39