Gal*_*lik 25 c++ conditional-operator c++11
我习惯于编写带有文件名或读取的小命令行工具std::cin,所以我一直在使用这种模式:
int main(int argc, char* argv[])
{
std::string filename;
// args processing ...
std::ifstream ifs;
if(!filename.empty())
ifs.open(filename);
std::istream& is = ifs.is_open() ? ifs : std::cin;
std::string line;
while(std::getline(is, line))
{
// process line...
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
在阅读Stack Overflow上的一个问题之后,我尝试修改我常用的模式,以满足从文件或文件中读取的需要std::istringstream.令我惊讶的是它不会编译并给出这个错误:
Run Code Online (Sandbox Code Playgroud)temp.cpp:164:47: error: invalid initialization of non-const reference of type ‘std::istream& {aka std::basic_istream<char>&}’ from an rvalue of type ‘void*’ std::istream& is = ifs.is_open() ? ifs : iss; // won't compile
在我看来,它试图将std::istringstreamobject(iss)转换为布尔值并获取它operator void*().
int main(int argc, char* argv[])
{
std::string filename;
std::string data;
// args processing ...
std::ifstream ifs;
std::istringstream iss;
if(!filename.empty())
ifs.open(filename);
std::istream& is = ifs.is_open() ? ifs : iss; // won't compile
std::string line;
while(std::getline(is, line))
{
// process line...
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
为什么它和?std::istringstream不同?它们都来源于.std::cinstd::ifstreamstd::istream
然后我记得已经转换我的模式以适应三种可能性,从文件,字符串或std::cin.我记得那很有效(尽管它非常笨拙).所以将三重解决方案应用于这个问题,我想出了一个完全有效的软糖:
int main(int argc, char* argv[])
{
std::string filename;
std::string data;
// args processing ...
std::ifstream ifs;
std::istringstream iss;
if(!filename.empty())
ifs.open(filename);
std::istream& is = ifs.is_open() ? ifs : true ? iss : std::cin; // fudge works
std::string line;
while(std::getline(is, line))
{
// process line...
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)为什么这个软糖有效?GCC是否违反了关于三元运算符(?:)如何解析其类型的规则?或者我错过了什么?
T.C*_*.C. 27
最小化的例子:
class A { };
class B : public A { };
class C : public A { };
int main() {
B b;
C c;
A& refA = true? b : c;
}
Run Code Online (Sandbox Code Playgroud)
Clang报道:
main.cpp:13:19: error: incompatible operand types ('B' and 'C')
A& refA = true? b : c;
Run Code Online (Sandbox Code Playgroud)
相关规则见标准的§5.16[expr.cond]/p3-6:
3否则,如果第二个和第三个操作数具有不同的类型并且具有(可能是cv限定的)类类型,或者两者都是相同值类别的glvalues和除cv-qualification之外的相同类型,则尝试转换每个操作数都是另一个的类型.确定类型T1的操作数表达式E1是否可以转换为匹配类型T2的操作数表达式E2的过程定义如下:
- 如果E2是左值:如果E1可以被隐式转换(第4条)到类型"左值引用T2",则E1可以被转换为匹配E2,受制于转换中引用必须直接绑定的约束(8.5.3) )到左值.
- 如果E2是x值:如果E1可以隐式转换为"rvalue reference to T2"类型,则E1可以转换为匹配E2,受限于引用必须直接绑定.
- 如果E2是prvalue,或者上面的转换都不能完成,并且至少有一个操作数具有(可能是cv-qualified)类类型:
- 如果E1和E2具有类类型,并且底层类类型相同或者一个是另一个类的基类:如果T2的类与类的类型相同,则可以转换为E1,或者基类类, T1的类别和T2的cv资格是与cv资格相同的cv资格或更高的cv资格.如果应用转换,则通过从E1复制初始化T2类型的临时值并将该临时值用作转换后的操作数,将E1更改为类型T2的prvalue.
- 否则(即,如果E1或E2具有非类型类型,或者如果它们都具有类类型但基础类不是相同或者是另一个类的基类):如果E1可以是E1,则可以将E1转换为匹配E2如果E2被转换为prvalue(或者它具有的类型,如果E2是prvalue),则隐式转换为表达式E2将具有的类型.
使用该过程,确定是否可以转换第二操作数以匹配第三操作数,以及是否可以转换第三操作数以匹配第二操作数.如果两者都可以转换,或者一个可以转换,但转换不明确,则程序格式不正确.如果两者都不能被转换,则操作数保持不变并且如下所述执行进一步检查.如果只能进行一次转换,则将该转换应用于所选操作数,并使用转换后的操作数代替本节其余部分的原始操作数.
4如果第二个和第三个操作数是相同值类别的glvalues并且具有相同的类型,则结果属于该类型和值类别,如果第二个或第三个操作数是位字段,则它是位字段,或者如果两者都是位字段.
5否则,结果是prvalue.如果第二个和第三个操作数不具有相同的类型,并且具有(可能是cv限定的)类类型,则使用重载决策来确定要应用于操作数的转换(如果有)(13.3.1.2,13.6) .如果重载决策失败,则程序格式错误.否则,应用如此确定的转换,并使用转换的操作数代替本节其余部分的原始操作数.
6在第二个和第三个操作数上执行左值到右值(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换.完成转换后,以下其中一项应成立:
- 第二和第三个操作数具有相同的类型; 结果是那种类型.如果操作数具有类类型,则结果是结果类型的prvalue临时值,它根据第一个操作数的值从第二个操作数或第三个操作数进行复制初始化.
- 第二和第三个操作数具有算术或枚举类型; 执行通常的算术转换以使它们成为公共类型,结果是该类型.
- 第二和第三操作数中的一个或两个具有指针类型; 执行指针转换(4.10)和限定转换(4.4)以将它们带到它们的复合指针类型(第5条).结果是复合指针类型.
- 第二和第三操作数中的一个或两个具有指向成员类型的指针; 执行指向成员转换(4.11)和限定转换(4.4)的指针,使它们成为复合指针类型(第5条).结果是复合指针类型.
- 第二个和第三个操作数都有类型,
std::nullptr_t或者一个具有该类型,另一个是空指针常量.结果是类型std::nullptr_t.
关键的一点是,这将始终尝试转换一个操作数以匹配另一个操作数的类型,而不是将两者都转换为第三种类型,直到您点击第5段,此时编译器开始寻找用户定义的隐式转换.指针或算术类型(这些只是operator?:§13.6中定义的内置候选函数的可能参数),并且出于您的目的,您真的不希望它到达那里.
在最小化的示例中,它直接与您的错误情况(A= istream,B= ifstream,C= istringstream)相关联,将一个转换为另一个的类型是不可能的,因此逻辑下降到p5,并且编译器查找用户定义的隐式转换.在最小化的示例中,没有转换,重载解析失败,整个事情都是格式错误.在您的错误情况下,预C++ 11(以及在libstdc ++后C++ 11中,显然)有一个从流的隐式转换void *,所以编译器这样做,给整个表达式一个void *类型,但显然可以绑定到引用std::istream,这是你看到的错误.
在你的第二种情况:
ifs.is_open() ? ifs : true ? iss : std::cin;
Run Code Online (Sandbox Code Playgroud)
std::cin具有类型std::istream,并且std::istringstream可以转换为其基类std::istream,因此内部条件表达式格式正确且具有类型std::istream.然后使用外部条件表达式,第二个操作数std::ifstream的类型也可以转换为第三个操作数的类型std::istream,因此整个表达式格式正确并且具有绑定到引用的正确类型.
Bri*_*ian 13
如果您有基类和派生类,则三元条件运算符知道将派生类转换为基类.但是如果你有两个派生类,它们不知道将它们转换为它们的公共基类.这不是gcc的表现; 这就是指定三元条件运算符如何在标准中工作.
std::istream& is = ifs.is_open() ? ifs : std::cin;
Run Code Online (Sandbox Code Playgroud)
这工作正常,因为std::cin有类型std::istream,它是一个基类std::ifstream.
std::istream& is = ifs.is_open() ? ifs : iss; // won't compile
Run Code Online (Sandbox Code Playgroud)
这不起作用,因为std::ifstream与std::istringstream"才"有一个共同的基类.
std::istream& is = ifs.is_open() ? ifs : true ? iss : std::cin; // fudge works
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为它被解析为:
std::istream& is = ifs.is_open() ? ifs : (true ? iss : std::cin);
Run Code Online (Sandbox Code Playgroud)
括号表达式具有类型std::istream.因此,如果选择,iss则转换为类型左值std::istream,并且ifs也进行类似转换.
编译器尝试从三元运算符中找到两个结果的公共类型,如果您看到例如此引用,您将看到有一个转换运算符覆盖void*(或者bool对于C++ 11及更高版本),因此编译器使用它.
但是当它尝试进行分配时,它会出错,因为在初始化的右侧你有一个void*(或者bool)类型,而在左侧有一个引用std::istream.
要解决此问题,您必须手动将每个流转换为引用std::istream,例如static_cast.