用右值初始化左值引用

Initializing lvalue reference with rvalue

我用 gcc/clang 构建了这段代码并得到了不同的结果:

#include <iostream>
#include <sstream>

int main() {
    std::istream& is = 1 ? std::move(std::stringstream("")) : std::cin;
}

海湾合作委员会 4.9.1

没有错误

铿锵声 3.4

prog.cc:5:63: error: call to implicitly-deleted copy constructor of 'istream' (aka 'basic_istream<char>')
    std::istream& is = 1 ? std::move(std::stringstream("")) : std::cin;
                                                             ^~~~~~~~
/usr/local/libcxx-3.4/include/c++/v1/istream:185:5: note: copy constructor is implicitly deleted because 'basic_istream<char, std::__1::char_traits<char> >' has a user-declared move constructor
   basic_istream(basic_istream&& __rhs);
   ^
prog.cc:5:28: error: calling a protected constructor of class 'std::__1::basic_istream<char, std::__1::char_traits<char> >'
   std::istream& is = 1 ? std::move(std::stringstream("")) : std::cin;
                          ^
/usr/local/libcxx-3.4/include/c++/v1/istream:185:5: note: declared protected here
   basic_istream(basic_istream&& __rhs);
   ^
prog.cc:5:28: error: calling a protected constructor of class 'std::__1::basic_istream<char, std::__1::char_traits<char> >'
   std::istream& is = 1 ? std::move(std::stringstream("")) : std::cin;
                          ^
/usr/local/libcxx-3.4/include/c++/v1/istream:185:5: note: declared protected here
   basic_istream(basic_istream&& __rhs);
   ^

GCC 的行为是一个错误,它是 been fixed on trunk。铛是正确的。这是一个混乱的情况,因为条件运算符的第二个和第三个操作数具有混合值类别:

  • std::move(std::stringstream(""))std::stringstream;
  • 类型的 xvalue*
  • std::cinstd::istream.
  • 类型的左值

相关标准引用(§5.16 [expr.cond]/p3-6)可在this answer中找到。够长了,我真的不想复制过来。我将概述它是如何应用于此代码的:

  • 显然 std::istream 无法以任何方式转换为与 std::stringstream 匹配,无论值类别如何;
  • 鉴于引用必须直接绑定到左值的约束,std::stringstream 类型的 xvalue 无法转换为类型 "lvalue reference to std::istream" - 这里没有左值供引用绑定;
  • std::istreamstd::stringstream 的基数 class,因此根据 p3 的第 3 个项目符号,类型 std::stringstream 的 xvalue 可以并且将会转换为 prvalue通过复制初始化临时 std::istream 类型,它替换原始操作数以供进一步分析。
  • 现在第二个操作数是std::istream类型的纯右值,第三个操作数是std::istream类型的左值,它们有不同的值类别,所以p4不适用。
  • 因此结果是每个 p5 的纯右值。由于它们具有相同的类型,因此不执行 p5 中指定的重载决议,然后继续进行 p6。
  • p6中适用的项目符号是

    The second and third operands have the same type; the result is of that type. If the operands have class type, the result is a prvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand.

    因此它从转换后的第一个操作数或第二个操作数 (std::cin) 复制初始化结果(临时纯右值)。

因此出现错误:

  • 从左值 (std::cin) 复制初始化纯右值 std::istream 结果将使用复制构造函数,并且无法复制流。
  • std::stringstream xvalue 为第二个操作数复制初始化 prvalue temporary std::istream 是一个移动,但 std::istream 的移动构造函数受到保护。

* 有关术语(左值、xvalue、prvalue 等),请参阅 What are rvalues, lvalues, xvalues, glvalues, and prvalues?