Passing Rvalues Through Std::Bind

Passing rvalues through std::bind

The reason this fails is because when you specify foo<Movable>, the function you're binding to is:

void foo(Movable&&) // *must* be an rvalue
{
}

However, the value passed by std::bind will not be an rvalue, but an lvalue (stored as a member somewhere in the resulting bind functor). That, is the generated functor is akin to:

struct your_bind
{
your_bind(Movable arg0) :
arg0(arg0)
{}

void operator()()
{
foo<int>(arg0); // lvalue!
}

Movable arg0;
};

Constructed as your_bind(Movable()). So you can see this fails because Movable&& cannot bind to Movable.†

A simple solution might be this instead:

auto f = std::bind(foo<Movable&>, Movable());

Because now the function you're calling is:

void foo(Movable& /* conceptually, this was Movable& &&
and collapsed to Movable& */)
{
}

And the call works fine (and, of course, you could make that foo<const Movable&> if desired). But an interesting question is if we can get your original bind to work, and we can via:

auto f = std::bind(foo<Movable>,
std::bind(static_cast<Movable&&(&)(Movable&)>(std::move<Movable&>),
Movable()));

That is, we just std::move the argument before we make the call, so it can bind. But yikes, that's ugly. The cast is required because std::move is an overloaded function, so we have to specify which overload we want by casting to the desired type, eliminating the other options.

It actually wouldn't be so bad if std::move wasn't overloaded, as if we had something like:

Movable&& my_special_move(Movable& x)
{
return std::move(x);
}

auto f = std::bind(foo<Movable>, std::bind(my_special_move, Movable()));

Which is much simpler. But unless you have such a function laying around, I think it's clear you probably just want to specify a more explicit template argument.


† This is different than calling the function without an explicit template argument, because explicitly specifying it removes the possibility for it to be deduced. (T&&, where T is a template parameter, can be deduced to anything, if you let it be.)

std::bind and rvalue reference

It might become clearer if you write down what std::bind schematically does.

// C++14, you'll have to write a lot of boilerplate code for C++11
template <typename FuncT, typename ArgT>
auto
bind(FuncT&& func, ArgT&& arg)
{
return
[
f = std::forward<FuncT>(func),
a = std::forward<ArgT>(arg)
]() mutable { return f(a); }; // NB: a is an lvalue here
}

Since you can call the function object std::bind gives you multiple times, it cannot “use up” the captured argument so it will be passed as an lvalue reference. The fact that you pass bind itself an rvalue only means that there is no copy made on the line where a is initialized.

If you try to compile your example with the schematic bind shown above, you'll also get a more helpful error message from your compiler.

main.cxx: In instantiation of ‘bind(FuncT&&, ArgT&&)::<lambda()> mutable [with FuncT = main()::<lambda(Widget&&)>; ArgT = Widget]’:
main.cxx:10:33: required from ‘struct bind(FuncT&&, ArgT&&) [with FuncT = main()::<lambda(Widget&&)>; ArgT = Widget]::<lambda()>’
main.cxx:11:31: required from ‘auto bind(FuncT&&, ArgT&&) [with FuncT = main()::<lambda(Widget&&)>; ArgT = Widget]’
main.cxx:18:59: required from here
main.cxx:11:26: error: no match for call to ‘(main()::<lambda(Widget&&)>) (Widget&)’
]() mutable { return f(a); }; // NB: a is an lvalue here
^
main.cxx:11:26: note: candidate: void (*)(Widget&&) <conversion>
main.cxx:11:26: note: conversion of argument 2 would be ill-formed:
main.cxx:11:26: error: cannot bind ‘Widget’ lvalue to ‘Widget&&’
main.cxx:18:33: note: candidate: main()::<lambda(Widget&&)> <near match>
auto lambda = bind([](Widget&&){ return; }, std::move(w));
^
main.cxx:18:33: note: conversion of argument 1 would be ill-formed:
main.cxx:11:26: error: cannot bind ‘Widget’ lvalue to ‘Widget&&’
]() mutable { return f(a); }; // NB: a is an lvalue here

passing rvalue raises cannot bind to lvalue

f(t2, t1);

t2 has a name, so it is an lvalue. It's type is rvalue, but in an expression it's type is an lvalue. In order to pass it as an rvalue reference, you need to use std::forward (move or casting would be inappropriate here, because T1 and T2 are actually universal references, not rvalue references, see edit).

#include <iostream>
using namespace std;

template <typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2)
{
f(std::forward<T2>(t2), std::forward<T1>(t1));
}

void g(int &&i, int &j)
{
cout << i << " " << j << endl;
}

int main(void)
{
int i = 1;
flip2(g, i, 42);
}

http://ideone.com/Aop2aJ

Why

Consider:

template<typename T>
void printAndLog(T&& text) {
print(text);
log(text);
}

int main() {
printAndLog(std::string("hello, world!\n"));
}

When you use a variable's name, the expression-type is lvalue (glvalue?); the rvalueness is discarded. Otherwise in the example above, we'd have lost text to print(). Instead, we have to be explicit when we want to our rvalue to behave like one:

template<typename T>
void printAndLog(T&& text) {
print(text);
log(std::forward<T>(text)); // if text is an rvalue, give it up.
}

Edit

I used std::forward because T1&& and T2&& are universal references, not rvalue references. https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

std::bind with a movable paramter

Your troubles come from std::function.

std::bind returns some callable which has Param as member variable. When std::function instance is created all members of that functor generated by bind are stored by copying. When copy constructor of Param is commented out, it is just not possible to create std::function wrapper. And this is your issue.

As solution, get rid of std::function, and just pass any callable as template parameter:

template<class F>
void Do(F&& callback)
{
callback(5);
}

Live demo

std::bind(): bind lambda with rvalue reference as argument

The specification for std::bind is rather dense. In brief, a plain bound argument (not a bind expression, not a reference_wrapper, and not a placeholder) is passed to the bound function as std::forward<Vi>(tid) where Vi is TiD cv &, cv is the cv-qualifiers of the call wrapper, TiD is the type decay_t<Ti>, Ti is the type actually passed to bind, and tid is "an lvalue of type TiD constructed from std::forward<Ti>(ti)", and ti is the argument passed to bind.

Applying this to your call, we see that Ti is Dog and ti is Dog("DogABC"). So TiD is also Dog, and Vi is cv Dog &, which means that std::forward<Vi>(Tid) is an lvalue, and the compiler complains because your lambda takes an rvalue reference parameter, and an rvalue reference parameter cannot bind to a lvalue.

Can't bind lvalue to rvalue in member function but ok in global function

It's because the automatic template deduction for globalDoSomething infers T as int&.

If you explicitly instantiate the template function with globalDoSomething<int>(b); like you did for the member function of the template class, it will also fail to compile.

Conversely, if you instantiate the template class with A<int&> a;, it will successfully compile.



Related Topics



Leave a reply



Submit