【问题标题】:Is it possible to format strings in C++ like python's f-strings?是否可以像 python f-strings 那样在 C++ 中格式化字符串?
【发布时间】:2020-07-07 17:53:36
【问题描述】:

在 Python 中,可以使用 f-strings 方便地格式化字符串:

a = 42
print(f"a = {a}") # prints "a = 42"

是否有可能在编译时在 C++ 中做这样的事情,或者format("a = {}", a); 是最接近 f-strings 的东西?

编辑:我并不是说这些格式化方法应该在运行时起作用。编译器不能在编译时通过名称查找当前范围内可见的变量吗?比如,如果遇到字符串f"foo = {foo}",它可以在代码中将此字符串替换为std::format("foo = {}", foo)

【问题讨论】:

  • C++ 不具备使这成为可能的反射功能。编译后,a 变量就消失了,它充其量只是一个值。在许多情况下,它会完全消失。
  • 我赞成这个问题,因为它太可爱了,但 C++ 不太可能做到这一点。到目前为止,我对 C++ 输出格式最不满意的是 fmt 库:github.com/fmtlib/fmt

标签: python c++ f-string


【解决方案1】:

C++ 没有反射。因此,不可能通过名称提供对变量的引用。

但正如@NicolBolas 指出的那样,即使 如果 存在反射,也需要更多。它不仅需要大量的语言支持(告诉被调用函数调用范围内的变量是什么),而且还会阻止大量优化。编译器无法删除任何变量,因为格式字符串(在编译时未知时)最终可能会引用范围内的任何变量。

所以,不。 std::format 是最接近的。

【讨论】:

  • 反思只是完成这项工作所需的开始
  • 是的,好点。我会尽量让答案更清楚。
  • 我并不是说这些格式化方法应该在运行时工作。编译器不能在编译时通过名称查找当前范围内可见的变量吗?比如,如果遇到字符串f"foo = {foo}",它可以在代码中将此字符串替换为std::format("foo = {}", foo)
  • 我们对 C++ 中的预处理器有很多~乐趣~的痛苦,委员会不可能批准这样的替换(嗯,我希望)
  • 对不起,我不明白为什么 Bamberghh 的建议不是一个简单的解决方案,可以在编译时使用。这两种语法在 1:1 关系中是等价的,不是吗?如果是这样,我看不到复杂性。请解释。为什么这不能只是 std::format 的语法糖?
【解决方案2】:

C++ 没有 f-string 机制。 String interpolation 在现有语言中是不可能的。即使使用编译时函数调用,您也需要让这样的函数“伸出”到调用者的范围内,以检索这些变量的内容并适当地格式化它们。这意味着基于函数的字符串插值机制的行为必须有所不同,不仅基于它采用的字符串,而且基于它被调用的位置。

这不是 C++ 真正允许的。

【讨论】:

  • 为什么这与std::format("foo = {}", foo) 的情况不同?
  • @Gulzar:因为 编译器 没有从字符串中找到一个名为“foo”的变量,并将其插入到命令中。如果你想传入foo,那么foo 必须显式地成为函数调用中的变量之一。该函数调用与任何其他 C++ 函数调用一样工作;不需要编译器魔法。
  • 我想要的魔法是写printf(f"foo={foo}") 并让编译器将其理解为printf(std::format("foo = {}", foo))。这是可行的吗?不好吗?如果不好,请解释原因?
  • @Gulzar:我知道你想要什么。但这需要更改语言。就目前而言,它不能在 C++ 中完成。 OP 专门询问在 C++ 中可以做些什么来使其工作,而不是语言更改是否可以使其工作。
【解决方案3】:

没有好办法。

使用一些繁重的宏,您可以实现F(a = ARG(a)) 之类的语法(其中FARG 是宏),它会有点工作,但有很大的限制(见下文) .

这是一个依赖于 libfmt 的示例实现:

#define F(...) F_LOW( (str, __VA_ARGS__ ) )
#define ARG(...) )(expr, __VA_ARGS__ )(str,

#define F_LOW(...) ::fmt::format(F_END(F_STR_LOOP_A __VA_ARGS__) F_END(F_ARG_LOOP_A __VA_ARGS__))

#define F_END(...) F_END_(__VA_ARGS__)
#define F_END_(...) __VA_ARGS__##_END

#define F_CAT(a, b) F_CAT_(a, b)
#define F_CAT_(a, b) a##b

#define F_STR_LOOP_A(...) F_STR_LOOP_BODY(__VA_ARGS__) F_STR_LOOP_B
#define F_STR_LOOP_B(...) F_STR_LOOP_BODY(__VA_ARGS__) F_STR_LOOP_A
#define F_STR_LOOP_A_END
#define F_STR_LOOP_B_END
#define F_STR_LOOP_BODY(tag, ...) F_CAT(F_STR_LOOP_BODY_, tag)(__VA_ARGS__)
#define F_STR_LOOP_BODY_str(...) #__VA_ARGS__
#define F_STR_LOOP_BODY_expr(...) "{}"

#define F_ARG_LOOP_A(...) F_ARG_LOOP_BODY(__VA_ARGS__) F_ARG_LOOP_B
#define F_ARG_LOOP_B(...) F_ARG_LOOP_BODY(__VA_ARGS__) F_ARG_LOOP_A
#define F_ARG_LOOP_A_END
#define F_ARG_LOOP_B_END
#define F_ARG_LOOP_BODY(tag, ...) F_CAT(F_ARG_LOOP_BODY_, tag)(__VA_ARGS__)
#define F_ARG_LOOP_BODY_str(...)
#define F_ARG_LOOP_BODY_expr(...) , __VA_ARGS__

例如,使用这些宏 F(a=ARG(a), b=ARG(b)) 将等同于 pythonic f"a={a}, b={b}",并将扩展为

::fmt::format("a=" "{}" ", b=" "{}" "", a, b)

相当于

::fmt::format("a={}, b={}", a, b)

目前看起来不错?它甚至可以处理转义序列(以某种方式)!

这里是提到的主要缺点

  • 任何空白字符序列都将替换为单个空格。

    • 此外,所有前导和尾随空格以及 ARG 周围的所有空格都将被删除。
  • 字符串中的所有宏都被展开。

  • 字符串不能包含不平衡的括号。

  • ARG(...) 不能出现在括号内。

IMO,这使得它无法用于除调试输出之外的任何内容。

可能的解决方法包括:

  • 将语法更改为以下之一:

    • F("a = " ARG(a))(我不喜欢它的样子)
    • F("a = ", (a))(看起来不错,但需要 Boost.Preprocessor 或等效生成的宏)
    • F("a = ", ARG a)(同)
    • F("a = ", ()a)(相同)
  • 添加一个子宏(类似于ARG),比如STR("..."),以逐字包含字符串文字。这将允许您包含任何您想要的空格/括号/未扩展的宏/...。

    例如:F(a = ARG(A)) 给你a =42。如果你想在= 后面有一个空格,你可以使用F(a = STR(" ") ARG(A))

    语法太晦涩难懂,因此将其实现留给读者作为练习。

  • ...


奖励:使用正确的标志(或#pragmas),大多数编译器都允许您在宏名称中包含$。这意味着您可以使用$(expr) 而不是ARG(expr),如果您愿意...

【讨论】:

    【解决方案4】:

    如果你只想打印一个包含变量的字符串,你总是可以使用古老的printf() 函数。

    char my_name[] = "Michael";
    int my_age = 107;
    printf("Hello my name is %s. I am %d years old.", my_name, my_age);
    

    sprintf() 是一种变体,它允许您将输出保存为字符串(字符数组)并再次使用它,或者在嵌入式系统的情况下将其发送到串行端口。

    我对 C 比对 python 更熟悉,所以如果我遗漏了什么,请解释一下。

    【讨论】:

    • 请记住,sprintf 实际上是不安全的——因为它不检查目标缓冲区的长度,所以您可以覆盖您不应该访问的内存。最好使用std::snprintf(尽管如果您传递的缓冲区大小比您正在写入的缓冲区大,它在技术上仍然不安全)
    【解决方案5】:

    不,C++ 中没有字符串插值。我在这个领域发现的唯一工作是 2015 年 google 的“非常非常粗略的提案草案”Interpolated String Literals

    我们刚刚在 C++20 中获得了 formatting library,甚至在任何标准库实现中都是 does not appear to be yet implemented(截至 2020 年 7 月)。字符串插值需要语言支持,与更改库相比,标准委员会总是更不愿意更改语言。

    【讨论】:

      【解决方案6】:

      我认为在 c++ 中使用“f-string”之类的最简单方法是对另一个问题C++: how to get fprintf results as a std::string w/o sprintf的公认答案@

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-25
        • 1970-01-01
        • 2016-01-13
        • 2010-12-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多