为什么右值不能绑定到非 const 左值引用,除了写入临时值无效这一事实之外?

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

我已阅读 the SO question here 并理解这部分答案:"But if you bind a temporary to a non-const reference, you can keep passing it around "forever" 只是为了让您对对象的操作消失,因为在此过程中的某个地方您完全忘记了这是暂时的."

也就是在下面:

#include <iostream>

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

int main() {
    modifyValue(9899);

    return 0;
}

如果右值可以绑定到非常量左值引用,那么可能会进行许多最终会被丢弃的修改(因为右值是临时的),这是无用的。

这似乎定义得很好(写入临时值就像写入任何值一样,生命周期与写入的有效性无关)。

这是禁止指定绑定的完全正确的理由(即使绑定会被明确定义),但是一旦我认为禁止这种绑定会强制需要转发引用,我的问题就开始形成了。

关于为什么右值不能绑定到非常量左值引用,是否还有其他原因(即除了写入临时值之外)?

简单的答案是在大多数情况下,将临时变量传递给需要可变左值引用的函数表示逻辑错误 , 而 c++ 语言正在尽最大努力帮助您避免犯错误。

函数声明:void foo(Bar& b) 建议以下叙述:

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

传递一个临时变量作为输出占位符通常比调用一个returns一个对象的函数更糟糕的逻辑错误,只是丢弃未经检查的对象。

例如:

Bar foo();

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

不过这两个版本都没有问题:

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.

所以问题是,我们要达到什么目的?如果我们只需要一个 Bar 来工作,我们可以使用 Bar&& bBar b 版本。

如果我们想可能使用一个临时的并且可能使用一个现有的Bar,那么我们可能需要两个重载foo,因为它们在语义上会略有不同:

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

如果我们需要这种可选性,我们可以通过将一个包裹在另一个中来创建它:

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;
}

根据这些定义,这些现在都是合法的:

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

好的,为什么这么关心?为什么无意义的修改临时文件会是逻辑错误?

好吧,想象一下:

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

我们期待做这样的事情:

extern void baz(Bar& b);

Bar b;
baz(foo(b));

现在想象这可以编译:

auto& br = foo(Bar());

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

因为我们被迫在 foo 的特殊重载中正确处理临时文件,foo 的作者可以确信这个错误永远不会在您的代码中发生。