Why can an rvalue not bind to a non-const lvalue reference, other than the fact that writing to a temporary has no effect?

杀马特。学长 韩版系。学妹 提交于 2019-12-06 01:32:20

The simple answer is that in most cases, passing a temporary to a function that expects a mutable lvalue reference indicates a logic error, and the c++ language is doing its best to help you avoid making the error.

The function declaration: void foo(Bar& b) suggests the following narrative:

foo takes a reference to a Bar, b, which it will modify. b is therefore both an input and an output

Passing a temporary as the output placeholder is normally a much worse logic error than calling a function which returns an object, only to discard the object unexamined.

For example:

Bar foo();

void test()
{
  /*auto x =*/ foo();  // probable logic error - discarding return value unexamined
}

However, in these two versions, there is no problem:

void foo(Bar&& b)

foo takes ownership of the object referenced by Bar

void foo(Bar b)

foo conceptually takes a copy of a Bar, although in many cases the compiler will decide that creating and copying a Bar is un-necessary.

So the question is, what are we trying to achieve? If we just need a Bar on which to work we can use the Bar&& b or Bar b versions.

If we want to maybe use a temporary and maybe use an existing Bar, then it is likely that we would need two overloads of foo, because they would be semantically subtly different:

void foo(Bar& b);    // I will modify the object referenced by b

void foo(Bar&& b);   // I will *steal* the object referenced by b

void foo(Bar b);   // I will copy your Bar and use mine, thanks

If we need this optionality, we can create it by wrapping one in the other:

void foo(Bar& b)
{
  auto x = consult_some_value_in(b);
  auto y = from_some_other_source();
  modify_in_some_way(b, x * y);
}

void foo(Bar&& b)
{
  // at this point, the caller has lost interest in b, because he passed
  // an rvalue-reference. And you can't do that by accident.

  // rvalues always decay into lvalues when named
  // so here we're calling foo(Bar&)

  foo(b);   

  // b is about to be 'discarded' or destroyed, depending on what happened at the call site
  // so we should at lease use it first
  std::cout << "the result is: " << v.to_string() << std::endl;
}

With these definitions, these are now all legal:

void test()
{
  Bar b;
  foo(b);              // call foo(Bar&)

  foo(Bar());          // call foo(Bar&&)

  foo(std::move(b));   // call foo(Bar&&)
  // at which point we know that since we moved b, we should only assign to it
  // or leave it alone.
}

OK, by why all this care? Why would it be a logic error to modify a temporary without meaning to?

Well, imagine this:

Bar& foo(Bar& b)
{
  modify(b);
  return b;
}

And we're expecting to do things like this:

extern void baz(Bar& b);

Bar b;
baz(foo(b));

Now imagine this could compile:

auto& br = foo(Bar());

baz(br); // BOOM! br is now a dangling reference. The Bar no longer exists

Because we are forced to handle the temporary properly in a special overload of foo, the author of foo can be confident that this mistake will never happen in your code.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!