最初发布于2022年11月2日的TotW #215
作者:Phoebe Liang
更新日期:2022年11月16日
Abseil现在包含一个新的轻量级机制,AbslStringify(),允许用户将用户定义的类型格式化为字符串。使用AbslStringify()扩展的用户定义类型可以与absl::StrFormat、absl::StrCat和absl::Substitute无缝配合使用。
与大多数类型扩展一样,您应该拥有要扩展的类型。
假设我们有一个简单的Point结构体:
struct Point {
int x;
int y;
};
如果我们希望通过absl::StrFormat()、absl::StrCat()和absl::Substitute()来格式化Point,我们需要添加一个名为AbslStringify()的友元函数模板:
struct Point {
template <typename Sink>
friend void AbslStringify(Sink& sink, const Point& p) {
absl::Format(&sink, "(%d, %d)", p.x, p.y);
}
int x;
int y;
};
注意:AbslStringify()使用通用的“sink”缓冲区来构造字符串。该sink具有类似于absl::FormatSink的接口,但不支持PutPaddedString()。
现在,absl::StrCat("The point is ", p)和absl::Substitute(“The point is $0”, p)将正常工作。
注意:absl::StrFormat()还提供了一个更可定制的扩展点AbslFormatConvert(),它不受absl::StrCat()支持。
使用%v格式化的类型推导
但是,如果我们想要使用absl::StrFormat()来格式化我们的类型呢?absl::StrFormat()的现有类型说明符不支持使用AbslStringify()扩展的用户定义类型,所以我们需要像这样做:
absl::StrFormat("The point is (%d, %d)", p.x, p.y)
显然,这并不理想。它根本没有使用扩展,并且重复了在AbslStringify()定义中使用的格式字符串。相反,我们可以使用新的类型说明符%v:
absl::StrFormat("The point is %v", p)
%v使用类型推导来格式化一个参数。%v支持大多数基本类型的格式化,以及使用AbslStringify()扩展的任何类型。您可以将%v视为一种通用方式来格式化absl::StrFormat()可以推断的任何类型的“值”。%v还可以直接在AbslStringify()定义中使用。
%v指示符推断以下类型:
- d:有符号整数值
- u:无符号整数值
- g:浮点数值
- double
- float
- long double
- s:字符串值
- std::string
- absl::string_view
- std::string_view
- absl::Cord
注意:不支持const char*。更多信息请参阅下文。
一些例子:
absl::StrFormat("%v", std::string{"hello"}) -> "hello"
absl::StrFormat("%v", 42) -> "42"
absl::StrFormat("%v", uint64_t{16}) -> "16"
absl::StrFormat("%v", 1.6) -> "1.6"
absl::StrFormat("%v", true) -> "true"
特殊处理的类型
%v故意不支持char和const char*,因为所需输出格式存在歧义。布尔值打印为"true"和"false",而不是absl::StrFormat()和absl::StrCat()通常打印的"1"和"0"。
与其他库的集成
AbslStringify()在其他Abseil库中也有额外的支持。
日志记录
定义了AbslStringify()的类型可以直接记录日志:
struct Point {
template <typename Sink>
friend void AbslStringify(Sink& sink, const Point& p) {
absl::Format(&sink, "(%v, %v)", p.x, p.y);
}
int x = 10;
int y = 20;
};
Point p;
LOG(INFO) << p;
这段代码将在日志中产生如下的消息:
I0926 09:00:00.000000 12345 main.cc:10] (10, 20)
建议通过实现AbslStringify()使自定义类型可记录日志,而不是使用operator<<,因为它是一个通用的字符串化扩展,还能支持absl::StrFormat、absl::StrCat和absl::Substitute。
Protocol Buffer类型
使用AbslStringify()可以对Protocol Buffers进行格式化。由于AbslStringify()在用户体验上比DebugString()更平滑,建议用户在将proto格式化为字符串时使用AbslStringify()。
message MyProto {
optional string my_string = 1;
}
MyProto my_proto;
my_proto.set_my_string("hello world");
absl::StrCat("My proto is: ", my_proto);
absl::StrFormat("My proto is: %v", my_proto);
LOG(INFO) << my_proto;
结束语
除了在需要使用的自定义类型中,%v类型指示符旨在用于精确格式化不重要的情况。它本质上是一个“只需以人类可读的方式打印出来”的通用指示符。如果您需要其他保证,请使用更具体的类型说明符之一。