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

时间:2019-02-15 17:58:46

标签: 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;
}

我想知道为什么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)?三元运算符在具有不同类型的情况下会复制其参数,而在具有相同类型的情况下却从不复制吗?我想念或需要知道的其他东西吗?

1 个答案:

答案 0 :(得分:7)

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

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

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

因此:

  • 在A中,两个操作数都是prvalue,因此不存在从左值到右值的转换。在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,它使用受保护的move构造函数(因此程序违反了成员访问控制)。在C ++ 17中,可以考虑更改规则,以便不将xvalue转换为prvalue,而是将prvalue实例化以产生xvalue,从而避免了移动的需要。但这更改没有明显的好处,并且是否是最合理的行为值得怀疑,因此这可能就是为什么不进行更改的原因(如果考虑的话)。