我一直对C++的输入/输出(尤其是格式化)持有批评态度,因为我认为它相对于C而言是一步退步。格式需要是动态的,从外部资源(如文件或参数)加载它们是非常合理的。
然而,我以前从未尝试过实现其他方式,你的问题让我花费了一些周末时间来尝试这个想法。
当然,问题比我想象的更加复杂(例如仅整数格式化例程就有200多行),但是我认为这种方法(动态格式字符串)更易用。
您可以从此链接下载我的实验(仅为.h文件),并从此链接下载测试程序(“测试”可能不是正确的术语,我只是用它来查看是否能够编译)。
以下是一个示例:
#include "format.h"
#include <iostream>
using format::FormatString;
using format::FormatDict;
int main()
{
std::cout << FormatString("The answer is %{x}") % FormatDict()("x", 42);
return 0;
}
它与 boost.format 方法不同,因为使用了命名参数,并且格式字符串和格式字典是分开构建的(例如可以传递在不同的上下文中)。此外,我认为格式选项应该作为字符串的一部分出现(就像 printf),而不是放在代码中。
“FormatDict”的一个技巧是保持语法合理性。
FormatDict fd;
fd("x", 12)
("y", 3.141592654)
("z", "A string");
FormatString
仅从const std::string&
解析(我决定预解析格式字符串,但较慢但可能可接受的方法是每次传递字符串并重新解析)。
通过特化转换函数模板,可以扩展用户定义类型的格式化;例如
struct P2d
{
int x, y;
P2d(int x, int y)
: x(x), y(y)
{
}
};
namespace format {
template<>
std::string toString<P2d>(const P2d& p, const std::string& parms)
{
return FormatString("P2d(%{x}; %{y})") % FormatDict()
("x", p.x)
("y", p.y);
}
}
然后,可以将P2d
实例简单地放置在格式化字典中。
同时,可以通过在%
和{
之间放置参数来传递格式化函数。
目前,我只实现了整数格式化专用,支持以下功能:
- 左/右/居中对齐的固定大小
- 自定义填充字符
- 通用基数(2-36),小写或大写
- 数字分隔符(带有自定义字符和计数)
- 溢出字符
- 符号显示
我还添加了一些常见情况的快捷方式,例如
"%08x{hexdata}"
是一个带有8位数字的十六进制数,前面补零。
"%026/2,8:{bindata}"
这是一个24位二进制数(如"/2"
所需),每8位带有数字分隔符":"
(如",8:"
所需)。
请注意,这只是一个想法的代码,例如,现在我只是防止复制,当可能允许存储格式字符串和字典时,这可能是合理的(对于字典,然而,重要的是要给予避免复制对象的能力,只因为它需要添加到FormatDict
中,虽然我认为这是可能的,但这也会引起关于生命周期的非平凡问题)。
更新
我对初始方法进行了一些更改:
- 格式化字符串现在可以复制
- 自定义类型的格式化使用模板类而不是函数完成(这允许部分特化)
- 我添加了一个序列的格式化程序(两个迭代器)。语法仍然粗糙。
我创建了一个GitHub项目,采用boost许可证。