为什么std :: ostream在三元运算符中使用时不能编译?

NPS*_*NPS 1 c++ ternary-operator ostream

#include <iostream>
using namespace std;

int main()
{
    std::ostream o(nullptr);
    true ? std::ostream(nullptr) : std::ostream(nullptr); // A
    true ? std::ostream(nullptr) : o; //B
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我想知道为什么A编译B错误而失败并出现错误:

prog.cpp: In function ‘int main()’:
prog.cpp:7:33: error: ‘std::basic_ostream<_CharT, _Traits>::basic_ostream(const std::basic_ostream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’ is protected within this context
  true ? o : std::ostream(nullptr);
                                 ^
Run Code Online (Sandbox Code Playgroud)

所以我找到了这个网站:https://docs.microsoft.com/en-us/cpp/cpp/conditional-operator-q?view = vs2017,它说当三元运算符有参数时(通过"参数"我的意思)的那些左,右:),那么它可能会导致的参数复制,铸造等..这将使因为感觉std::ostream已经拷贝构造函数定义为protected.因此在A三元运算符中获取相同类型的两个参数,不进行复制,因此没有问题.而在B三元运算符中获取不同类型的参数,这显然导致需要复制,这是不允许的std::ostream.到目前为止一切似乎都没问题.

但后来我尝试了这个:

#include <iostream>
using namespace std;

int main()
{
    std::ostream o(nullptr);
    std::ostream & oRef = o;
    std::ostream && oRRef = std::ostream(nullptr);
    true ? std::ostream(nullptr) : std::ostream(nullptr); // A
    true ? std::ostream(nullptr) : o; //B
    true ? std::ostream(nullptr) : oRef; // C
    true ? std::ostream(nullptr) : oRRef; // D
    true ? std::ostream(nullptr) : std::move(oRRef); // E
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

C,D并且E也因类似的错误.

所以我有几个问题.什么是表达式的类型:std::ostream(nullptr)?三元运算符在它们具有不同类型时会复制其参数,但是当它们属于同一类型时永远不会复制时,这是真的吗?我错过或需要知道的其他事情?

Bri*_*ian 7

该标准包含一些关于如何评估条件表达式的复杂规则([expr.cond]).但是我没有在这里引用这些规则,而是要解释你应该如何思考它们.

条件表达式的结果可以是左值,x值或prvalue.但是在编译时必须知道其中哪一个.(表达式的值类别永远不会取决于运行时发生的情况).很容易看出,如果第二个和第三个表达式都是相同类型的左值,那么结果也可以是左值,并且不必进行复制.如果第二个和第三个表达式都是相同类型的prvalues,那么,从C++ 17开始,也不会发生复制---类型的prvalue表示类型T对象的延迟初始化T,而编译器只是根据条件选择传递这两个prvalues中的哪一个最终用于初始化对象.

但是当一个表达式是左值而另一个表达式是相同类型的prvalue时,结果必须是prvalue.如果标准表示结果将是左值,那将是不合逻辑的,因为条件可能导致选择prvalue操作数,并且您不能将prvalue转换为左值.但你可以反过来做到这一点.所以标准说当一个操作数是一个左值而另一个是同一类型的prvalue时,左值必须经过左值到右值的转换.如果您尝试对std::ostream对象进行左值到右值的转换,则程序将因为复制构造函数被删除而格式错误.

从而:

  • 在A中,两个操作数都是prvalues,因此没有左值到右值的转换; 这在C++ 17中是可以的(但不是在C++ 14中).
  • 在B中,需要进行左值到右值的转换o,因此不会编译.
  • 在C中,oRef是一个左值,因此仍然需要左值到右值的转换,所以这也不会编译.
  • 在D中,oRRef仍然是左值(因为右值参考的名称是左值).
  • 在E中,一个参数是prvalue,一个是xvalue.仍然需要将xvalue转换为prvalue以使结果成为prvalue.

E的案例值得进一步评论.在C++ 11中很明显,如果一个参数是xvalue而另一个是同一类型的prvalue,则xvalue必须经历(误导性命名)左值到右值的转换以产生prvalue.在这种情况下std::ostream,这使用受保护的移动构造函数(因此程序违反了成员访问控制).在C++ 17中,人们可以考虑更改规则,以便不是将xvalue转换为prvalue,而是实现prvalue以产生xvalue,从而避免了移动的需要.但是这种变化没有明显的好处,这是否是最合理的行为是值得怀疑的,所以这可能是为什么它没有被制造(如果它甚至被考虑).