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-22 11:12:06

问题


I have read the SO question here and understood this part of the answer: "But if you bind a temporary to a non-const reference, you can keep passing it around "forever" just to have your manipulation of the object disappear, because somewhere along the way you completely forgot this was a temporary."

That is, in the following:

#include <iostream>

void modifyValue(int& rValue) {
    rValue++;
}

int main() {
    modifyValue(9899);

    return 0;
}

If an rvalue could bind to a non-const lvalue reference, then potentially many modifications could be done that would eventually be discarded (since an rvalue is temporary), this being useless.

This seems to be well defined however (writing to a temporary value is just like writing to any value, the lifetime has no relevancy to the validity of the writing).

That is a perfectly alright reason to prohibit the specified binding (even though the binding would be well defined), however once I considered that such a binding being prohibited forces the need for forwarding references, my question started forming.

Are there any other reasons (that is, apart from writing to a temporary value) as to why an rvalue cannot bind to a non-const lvalue reference?


回答1:


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.



来源:https://stackoverflow.com/questions/51836609/why-can-an-rvalue-not-bind-to-a-non-const-lvalue-reference-other-than-the-fact

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