我刚刚遇到了将自定义输出运算符与io-manipulators相结合的问题。也许我的期望完全没有实现,但是如果
std::cout << foo() << "\n";
打印
00
那我期望
std::cout << std::left << std::setw(20) << foo() << "!\n"
打印
00 !
但已实现
#include <iostream>
#include <iomanip>
struct foo { int a,b; };
std::ostream& operator<<(std::ostream& out, const foo& f) {
out << f.a << f.b;
return out;
}
int main() {
std::cout << foo() << "\n";
std::cout << std::left << std::setw(20) << foo() << "!";
}
屏幕上显示的内容是
00
0 0!
我基本上看到两种选择:A)我的期望是错误的。 B)我改用此实现:
std::ostream& operator<<(std::ostream& out, const foo& f) {
std::stringstream ss;
ss << f.a << f.b;
out << ss.str();
return out;
}
但是,考虑到大多数情况下不使用io机械手,这似乎有些开销。
在自定义输出运算符中“正确”对待io操作器的惯用方式是什么?
答案 0 :(得分:2)
恐怕没有简单的答案。如果您只需要处理std::setw
和std::left
,则解决方案是惯用的,但是对于其他操作,您必须确定格式化程序的行为。
例如,想象一下,如果您的结构具有 floats 而不是ints:
struct foo { float a,b; };
然后,您的用户尝试执行此操作:
const long double pi = std::acos(-1.L);
std::cout << std::setprecision(10) << foo{0.0f, pi} << "!\n"
这是您必须决定的时间:是要尊重输出流的precision属性,还是要忽略它?您当前的实现会忽略它,因为它会在另一个流中进行实际的转换。
要使用precision属性,您必须将其复制:
std::ostream& operator<<(std::ostream& out, const foo& f) {
std::stringstream ss;
ss.precision(out.precision());
ss << f.a << f.b;
out << ss.str();
return out;
}
对于整数,还必须考虑是否愿意兑现std::setbase
。
对于其他操纵器,例如std::setfill
,必须采用相同的推理。
答案 1 :(得分:1)
不一定惯用,但一种可能的解决方法是:
struct foo {
int a,b;
std::string toString() {
std::stringstream ss;
ss << a << b;
return ss.str();
}
};
std::ostream& operator<<(std::ostream& out, const foo& f) {
out << a << b;
return out;
};
现在,调用者可以选择输出是否应为整体:
std::cout << std::left << std::setw(20) << foo().toString() << "!"; // output as expected
std::cout << foo(); // output as expected
// and no unnecessary overhead
也许还会有人争辩说输出已经很慢了,所以一点额外的开销都不会造成伤害,只需根据stringify方法简单地实现输出运算符即可:
std::ostream& operator<<(std::ostream& out, const foo& f) {
out << f.toString();
return out;
}
这也解决了第一种方法的小缺点,该方法基本上用几乎相同的代码两次实现了相同的事情。