#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;
}
C
,D
和E
也会因类似错误而失败。
所以我有几个问题。表达式的类型是什么:std::ostream(nullptr)
?三元运算符在具有不同类型的情况下会复制其参数,而在具有相同类型的情况下却从不复制吗?我想念或需要知道的其他东西吗?
答案 0 :(得分:7)
该标准包含一些有关如何评估条件表达式的复杂规则([expr.cond])。但是,我不会在这里引用这些规则,而是要解释您应该如何考虑它们。
条件表达式的结果可以是lvalue,xvalue或prvalue。但是必须在编译时知道其中的哪一个。 (表达式的值类别永远不能取决于运行时发生的情况)。很容易看出,如果第二个和第三个表达式都是相同类型的左值,那么结果也可以设为左值,而不必进行复制。如果第二个表达式和第三个表达式都是相同类型的prvalue,则从C ++ 17开始,也不必进行复制--- T
类型的prvalue表示该类型的对象的延迟初始化T
,然后编译器根据条件简单地选择传递这两个prvalue中的哪一个,最终用于初始化对象。
但是,当一个表达式是左值,而另一个表达式是相同类型的prvalue时,则结果必须是prvalue。如果标准说结果将是左值,那将是不合逻辑的,因为条件可能导致选择prvalue操作数,并且您无法将prvalue转换为左值。但是您可以反过来做。因此,该标准表示,当一个操作数是左值,而另一个是相同类型的prvalue时,则左值必须进行左值到右值的转换。而且,如果您尝试在std::ostream
对象上进行左值到右值转换,则由于复制构造函数被删除,程序将格式错误。
因此:
o
需要从左值到右值的转换,因此不会编译。oRef
是左值,因此仍然需要从左值到右值的转换,因此也不会编译。oRRef
仍然是左值(因为右值引用的名称是左值)。 E的情况值得进一步说明。在C ++ 11中,很明显,如果一个参数是xvalue,另一个参数是相同类型的prvalue,则xvalue必须经过(误称)从左值到右值的转换才能产生prvalue。对于std::ostream
,它使用受保护的move构造函数(因此程序违反了成员访问控制)。在C ++ 17中,可以考虑更改规则,以便不将xvalue转换为prvalue,而是将prvalue实例化以产生xvalue,从而避免了移动的需要。但这更改没有明显的好处,并且是否是最合理的行为值得怀疑,因此这可能就是为什么不进行更改的原因(如果考虑的话)。