如何处理自定义输出运算符中的 iomanips?

How to deal with iomanips in custom output operators?

本文关键字:运算符 iomanips 输出 自定义 何处理 处理      更新时间:2023-10-16

我刚刚遇到了将自定义输出运算符与io操纵器结合使用的问题。也许我的期望完全偏离了,但如果

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操纵器的惯用方法是什么?

恐怕没有简单的答案。如果您只需要处理std::setwstd::left,您的解决方案是惯用的,但是对于其他操作,您必须决定格式化程序的行为。

例如,想象一下,如果你的结构有浮点数而不是整数:

struct foo { float a,b; };

然后,您的用户尝试执行此操作:

const long double pi = std::acos(-1.L);
std::cout << std::setprecision(10) << foo{0.0f, pi} << "!n"

这是您必须决定的时候:是要尊重输出流的精度属性,还是要忽略它?您当前的实现将忽略它,因为它在另一个流中执行实际转换。

若要遵循精度属性,必须复制它:

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

不一定是惯用语,但一种可能的发音是这样的:

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;
}

这也修复了第一种方法的小丑陋,该方法基本上用几乎相同的代码实现相同的事情两次。