在C ++中命名参数字符串格式

时间:2010-09-11 18:22:41

标签: c++ string-formatting

我想知道是否有像Boost Format这样的库,但它支持命名参数而不是位置参数。这是例如一个常见的习语。 Python,你有一个格式化字符串的上下文,可能会也可能不会使用所有可用的参数,例如

mouse_state = {}
mouse_state['button'] = 0
mouse_state['x'] = 50
mouse_state['y'] = 30

#...

"You clicked %(button)s at %(x)d,%(y)d." % mouse_state
"Targeting %(x)d, %(y)d." % mouse_state

是否有任何库提供最后两行的功能?我希望它提供类似的API:

PrintFMap(string format, map<string, string> args);

在谷歌搜索中,我发现许多库提供了位置参数的变化,但没有一个支持命名参数。理想情况下,库具有很少的依赖性,因此我可以轻松地将其放入我的代码中。 C ++不会像收集命名参数那样惯用,但可能有人在那里比我想得更多。

性能很重要,特别是我希望保持内存分配(在C ++中总是很棘手),因为这可能在没有虚拟内存的设备上运行。但是,即使从一个缓慢的开始,也可能比从头开始自己写的更快。

6 个答案:

答案 0 :(得分:6)

我一直都是C ++ I / O(特别是格式化)的评论家,因为在我看来,C语言的步骤是向后。格式需要是动态的,并且例如完全有意义从外部资源加载它们作为文件或参数。

我之前从未尝试过实际实施替代方案,而你的问题让我试图在周末时间内投入这个想法。

当然问题比我想象的要复杂得多(例如整数格式化例程只有200多行),但我认为这种方法(动态格式字符串)更有用。

你可以从this link(它只是一个.h文件)和this link的测试程序下载我的实验(测试可能不是正确的术语,我用它来看我是不是能够编译)。

以下是一个例子

#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实例可以简单地放在格式化词典中。

也可以通过将参数放在%{之间来将参数传递给格式化函数。

目前我只实现了支持

的整数格式化特化
  1. 固定尺寸​​,左/右/中心对齐
  2. 自定义填充字符
  3. 通用基础(2-36),小写或大写
  4. 数字分隔符(包含自定义字符和计数)
  5. 溢出字符
  6. 签名显示
  7. 我还为常见案例添加了一些快捷方式,例如

    "%08x{hexdata}"
    

    是一个十六进制数字,8位用“0”填充。

    "%026/2,8:{bindata}"
    

    是一个24位二进制数(根据"/2"的要求),每8位数字分隔符":"(根据",8:"的要求)。

    请注意,代码只是一个想法,例如,现在我只是阻止了副本,因为允许存储格式字符串和字典可能是合理的(对于字典来说,重要的是提供避免复制对象的能力,因为它需要被添加到FormatDict,而IMO这是可能的,它也会引起关于生命周期的非平凡问题。)

    更新

    我对初始方法做了一些修改:

    1. 现在可以复制格式字符串
    2. 使用模板类而不是函数(这允许部分特化)
    3. 来完成自定义类型的格式化
    4. 我为序列添加了一个格式化程序(两个迭代器)。语法仍然很粗糙。
    5. 我创建了github project for it,并获得了许可。

答案 1 :(得分:6)

fmt library支持命名参数:

print("You clicked {button} at {x},{y}.",
      arg("button", "b1"), arg("x", 50), arg("y", 30));

或者,如果您有具有相同名称的局部变量:

const char *button = b1;
int x = 50, y = 30;
print("You clicked {button} at {x},{y}.", FMT_CAPTURE(button, x, y));

作为一种语法糖,你甚至可以(ab)使用用户定义的文字来传递参数:

print("You clicked {button} at {x},{y}.",
      "button"_a="b1", "x"_a=50, "y"_a=30);

为简洁起见,上面的示例中省略了名称空间fmt

免责声明:我是这个图书馆的作者。

答案 2 :(得分:2)

答案似乎是,不,没有一个C ++库可以做到这一点,而C ++程序员显然甚至根据我收到的评论看不到需要它。我将不得不再写一次。

答案 3 :(得分:1)

那么我也会添加自己的答案,不是我知道(或编码过)这样的库,而是回答“保持内存分配”这一点。

一如既往,我可以设想某种速度/记忆权衡。

一方面,你可以解析“及时”:

class Formater:
  def __init__(self, format): self._string = format

  def compute(self):
    for k,v in context:
      while self.__contains(k):
        left, variable, right = self.__extract(k)
        self._string = left + self.__replace(variable, v) + right

这样你就不会保留一个“解析”结构了,希望大多数时候你只需要插入新数据(与Python不同,C ++字符串不是不可变的)。

然而,它远没有效率......

另一方面,您可以构建一个表示已解析格式的完全构造的树。您将拥有以下几个类:ConstantStringIntegerReal等等......可能还有一些子类/装饰器以及格式化本身。

然而,我认为最有效的方法是将两者混合在一起。

  • 将格式字符串分解为ConstantVariable
  • 列表
  • 索引另一个结构中的变量(具有开放寻址的哈希表可以很好地执行,或类似于Loki::AssocVector)。

你有:你只完成了2个动态分配的数组(基本上)。如果您想允许多次重复相同的密钥,只需使用std::vector<size_t>作为索引的值:良好的实现不应为小型向量动态分配任何内存(VC ++ 2010不会低于16字节的数据)。

在评估上下文本身时,查找实例。然后,您可以“及时”解析格式化程序,检查它的当前类型以替换它,并处理格式。

利弊:   - 及时:你一次又一次地扫描字符串   - One Parse:需要很多专用类,可能需要很多分配,但格式在输入时有效。像Boost一样,它可以重复使用。   - 混合:效率更高,特别是如果你不替换某些值(允许某种“null”值),但是延迟解析格式会延迟报告错误。

就个人而言,我会选择One Parse计划,尝试尽可能地使用boost::variant和策略模式来保持分配。

答案 4 :(得分:0)

鉴于Python它的自身是用C语言编写的,并且格式化是一种常用的功能,你可能能够(忽略拷贝写入问题)从python解释器中删除相关代码并将其移植到使用STL映射而不是Pythons native dicts。

答案 5 :(得分:0)

我为这个傀儡写了一个图书馆,请在GitHub上查看。

贡献很好。