本周小贴士 #215:使用AbslStringify()将自定义类型转换为字符串

最初发布于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类型指示符旨在用于精确格式化不重要的情况。它本质上是一个“只需以人类可读的方式打印出来”的通用指示符。如果您需要其他保证,请使用更具体的类型说明符之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值