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

问题描述 投票:1回答:1
#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;
}

我想知道为什么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);
                                 ^

所以我找到了这个网站:https://docs.microsoft.com/en-us/cpp/cpp/conditional-operator-q?view=vs-2017,当三元运算符有参数时(通过“参数”我指的是:左右两侧的参数),那么它可以导致复制参数,转换等等......这是有道理的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;
}

CDE也因类似错误而失败。

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

c++ ternary-operator ostream
1个回答
7
投票

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

条件表达式的结果可以是左值,右值或右值。但是在编译时必须知道其中哪一个。 (表达式的值类别永远不会取决于运行时发生的情况)。很容易看出,如果第二个和第三个表达式都是相同类型的左值,那么结果也可以是左值,并且不必进行复制。如果第二个和第三个表达式都是相同类型的prvalues,那么,从C ++ 17开始,也不会发生复制---类型为T的prvalue表示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,从而避免了移动的需要。但是这种变化没有明显的好处,这是否是最合理的行为是值得怀疑的,所以这可能是为什么它没有被制造(如果它甚至被考虑)。

© www.soinside.com 2019 - 2024. All rights reserved.