我习惯于编写一些小的命令行工具,这些工具要么使用文件名,要么从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;
}
字符串
在阅读了一个关于Stack Overflow的问题后,我试图修改我常用的模式,以适应从文件或std::istringstream
读取的需要。令我惊讶的是,它无法编译并给出以下错误:
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::istringstream
对象(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;
}
型
1.为什么std::istringstream
与std::cin
和std::ifstream
有区别?它们都是从std::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;
}
型
1.为什么这个捏造的工作?GCC是否违反了三元运算符(?:
)如何解析其类型的规则?或者我错过了什么?
4条答案
按热度按时间xesrikrc1#
最小化示例:
字符串
Clang报告:
型
相关规则见标准§5.16 [expr.cond]/p3-6:
3否则,如果第二个和第三个操作数具有不同的类型,(可能是cv限定的)类类型,或者如果两者都是相同值类别和相同类型的glvalues,除了cv限定,用于确定类型T1的操作数表达式E1是否可以被转换以匹配操作数表达式E2的过程类型T2的定义如下:
使用此过程,确定第二个操作数是否可以转换为与第三个操作数匹配,以及第三个操作数是否可以转换为与第二个操作数匹配。如果两者都可以转换,或者一个可以转换但转换不明确,则程序是病态的。如果两者都不能转换,操作数保持不变并且如下所述执行进一步的检查。如果恰好一个转换是可能的,该转换被应用于所选择的操作数,并且对于本部分的剩余部分,使用转换后的操作数来代替原始操作数。
4如果第二个和第三个操作数是相同值类别的glvalues,并且具有相同的类型,则结果是该类型和值类别,并且如果第二个或第三个操作数是位字段,或者如果两者都是位字段,则它是位字段。
5否则,结果是纯右值。如果第二个和第三个操作数的类型不同,并且其中一个具有(可能是cv限定的)类类型,重载解析用于确定要应用于操作数的转换(如果有的话)(13.3.1.2,13.6).如果重载解析失败,则程序是病态的。否则,应用由此确定的转换,并且对于本节的剩余部分,使用转换后的操作数来代替原始操作数。
6左值到右值(4.1)、数组到指针(4.2)和函数到指针(4.3)的标准转换是在第二个和第三个操作数上执行的。在这些转换之后,以下之一应该成立:
std::nullptr_t
类型,或者一个是std::nullptr_t
类型,另一个是空指针常量。结果是std::nullptr_t
类型。关键的一点是,这将始终尝试转换一个操作数以匹配另一个操作数的类型,而不是将两者都转换为第三种类型,直到您遇到第5段,此时编译器开始查找用户定义的指针或算术类型的隐式转换(这些只是第13.6节中定义的
operator?:
的内置候选函数的可能参数),对于您的目的,您真的不希望它到达那里。在最小化的示例中,它与错误情况直接相关(
A
=istream
,B
=ifstream
,C
=istringstream
),将一个转换为另一个的类型是不可能的,因此逻辑下降到p5,编译器寻找用户定义的隐式转换。在最小化的示例中没有转换,重载解析失败,整个事情都是病态的。在你的错误情况下,C11之前(显然,在libstdc post-C++11中)有一个从流到void *
的隐式转换,所以编译器这样做,给整个表达式一个void *
类型,但显然不能绑定到std::istream
的引用,这就是你看到的错误。在你的第二个案例中:
型
std::cin
的类型为std::istream
,std::istringstream
可以转换为基类std::istream
,因此内部条件表达式是格式良好的,类型为std::istream
。然后,对于外部条件表达式,第二个操作数std::ifstream
的类型也可以转换为第三个操作数std::istream
的类型,因此整个表达式是格式良好的,并且具有正确的类型来绑定到引用。evrscar22#
如果你有一个基类和一个派生类,三元条件运算符知道如何将派生类转换为基类。但是如果你有两个派生类,它不知道如何将它们转换为它们的公共基类。这不是gcc的问题;这只是三元条件运算符在标准中指定的工作方式。
字符串
这很好,因为
std::cin
有std::istream
类型,它是std::ifstream
的基类。型
这不起作用,因为
std::ifstream
和std::istringstream
“只有”一个公共基类。型
这是有效的,因为它被解析为:
型
带括号的表达式的类型为
std::istream
。因此,如果选择iss
,则将其转换为std::istream
类型的左值,ifs
也将进行类似的转换。jvlzgdj93#
编译器试图为三进制运算符的两个结果找到一个公共类型,如果你看到例如this reference,你会看到有一个casting operator override for
void*
(orbool
for C++11 and later),所以编译器使用它。但是当它尝试赋值时,它出错了,因为在初始化的右边有一个
void*
(或者bool
)类型,而在左边有一个对std::istream
的引用。为了解决这个问题,你必须手动将每个流转换为
std::istream
的引用,例如static_cast
。szqfcxe24#
gcc正在抱怨,因为ifs和iss是两种不同的类型。static_casting类型到std::istream&将解决您的问题。