C++ 为什么std::istringstream在三元(?:)运算符中的解析方式与std::ifstream不同?
我习惯于编写小的命令行工具,这些工具要么使用文件名,要么从C++ 为什么std::istringstream在三元(?:)运算符中的解析方式与std::ifstream不同?,c++,c++11,conditional-operator,C++,C++11,Conditional Operator,我习惯于编写小的命令行工具,这些工具要么使用文件名,要么从std::cin读取,因此我已经使用这种模式很长一段时间了: int main(int argc, char* argv[]) { std::string filename; // args processing ... std::ifstream ifs; if(!filename.empty()) ifs.open(filename); std::istream&
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;
}
在阅读了一个关于堆栈溢出的问题后,我试图修改我的常用模式,以满足从文件或std::istringstream
读取的需要。令我惊讶的是,它无法编译,并出现以下错误:
std::istringstream
的方式不同于std::cin
和std::ifstream
?它们都源自std::istream
std::istream& is = ifs.is_open() ? ifs : iss; // won't compile
然后我记得我已经转换了模式以适应三种可能,从文件、字符串或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;
}
?:
)解析其类型的规则?还是我遗漏了什么编译器试图从三元运算符中为这两个结果找到一个公共类型,如果您看到,例如,您将看到有一个,因此编译器使用该类型 但是当它尝试执行赋值时,它会出错,因为在初始化的右侧有一个
void*
(或者bool
)类型,在左侧有一个对std::istream
的引用
std::istream& is = ifs.is_open() ? ifs : iss; // won't compile
要解决此问题,您必须手动将每个流转换为对
std::istream
的引用,例如static\u cast
gcc正在抱怨,因为ifs和iss是两种不同的类型。静态\u将类型转换为std::istream&将解决您的问题。如果您有一个基类和一个派生类,三元条件运算符知道如何将派生类转换为基类。但是如果您有两个派生类,它不知道如何将它们转换为它们的公共基类。这不是gcc的行为;这就是三元条件运算符在标准中的工作方式
std::istream& is = ifs.is_open() ? ifs : std::cin;
这很好,因为std::cin
具有类型std::istream
,它是std::ifstream
的基类
std::istream& is = ifs.is_open() ? ifs : iss; // won't compile
这不起作用,因为std::ifstream
和std::istringstream
“only”有一个公共基类
std::istream& is = ifs.is_open() ? ifs : true ? iss : std::cin; // fudge works
这是因为它被解析为:
std::istream& is = ifs.is_open() ? ifs : (true ? iss : std::cin);
括号中的表达式具有类型std::istream
。因此,如果选中,iss
将被转换为类型为std::istream
的左值,并且ifs
也将被类似地转换。示例:
class A { };
class B : public A { };
class C : public A { };
int main() {
B b;
C c;
A& refA = true? b : c;
}
叮当声报道:
main.cpp:13:19: error: incompatible operand types ('B' and 'C')
A& refA = true? b : c;
相关规则见本标准§5.16【解释条件】/p3-6:
3否则,如果第二个和第三个操作数具有不同的类型和
具有(可能是cv限定的)类类型,或者两者都是glvalues
具有相同的价值类别和相同的类型,但
cv限定,尝试转换每个操作数
与另一个的类型相同。用于确定是否存在以下情况的过程:
T1类型的操作数表达式E1可以转换为与操作数匹配
T2型表达式E2定义如下:
- 如果E2是左值:如果E1可以隐式转换(第4条)为类型“对T2的左值引用”,则E1可以转换为匹配E2, 受限于转换中引用必须 直接绑定(8.5.3)到左值
- 如果E2是一个xvalue:如果E1可以隐式转换为类型“rvalue reference to T2”,则可以将E1转换为与E2匹配的值,但需遵守以下条件: 引用必须直接绑定的约束
- 如果E2是prvalue,或者如果上述两种转换都无法完成,并且至少有一个操作数具有(可能是cv限定的)
班级类型:
- 如果E1和E2具有类类型,并且基础类类型相同,或者其中一个是另一个的基类:E1可以转换为 如果T2的类与的类型相同,或是的基类,则匹配E2, T1的等级与T2的cv资格相同 简历资格为或大于 T1的简历资格。如果应用了转换,E1将更改为 通过从中复制初始化T2类型的临时变量来获取T2类型的PR值 E1,并使用该临时值作为转换的操作数
- 否则(即,如果E1或E2具有非类类型,或者如果它们都具有类类型,但基础类既不相同也不相同) 一个是另一个的基类):如果E1 可以隐式转换为表达式E2将具有的类型 如果E2被转换为prvalue(或其类型,如果E2是 prvalue)
5否则,结果为PRV值。如果第二个和第三个操作数 不具有相同的类型,并且其中一个具有(可能符合cv条件) 类类型,重载解析用于确定co
ifs.is_open() ? ifs : true ? iss : std::cin;