Tip of the Week #18: String Formatting with Substitute

Originally posted as TotW #18 on October 4, 2012

By Titus Winters

Updated 2022-11-16

Quicklink: abseil.io/tips/18

It happens all the time: you’re writing code and suddenly you need to assemble a new string from a template and some run-time values. Maybe it’s an error message from a failed Stubby call, maybe it’s the body of an email being sent from an internal process. Probably the most common mechanism for string formatting outside of google3 is sprintf/snprintf. But as we wander through the codebase, we see many things that people have done, and many places where C++ engineers are spending too much of their time, too many cycles, and too many lines of code to accomplish this task. In this week’s tip, we will walk through some common options and point out their various drawbacks.

Option 1: String Concatenation (built-in)

Some people still just reach for basic string concatenation:

std::string GetErrorMessage(const std::string& op, const std::string& user,
                            int id) {
  return "Error in " + op + " for user " + user + "(" + std::to_string(id) +
         ")";
}

As we saw back in Tip #3, there are problems with this approach: chains of calls to operator+() wind up making (and wasting) temporaries, and cause needless copies of your data.

Option 2: absl::StrCat() (google3/third_party/absl/strings/str_cat.h)

Just like in Tip #3, absl::StrCat() avoids those copies, handles the numeric conversion for us (go/willitstrcat), and even allows us to operate on string_view efficiently (which is better in cases when we are called with a C-style string):

std::string GetErrorMessage(absl::string_view op, absl::string_view user,
                            int id) {
  return absl::StrCat("Error in ", op, " for user ", user, "(", id, ")");
}

However, it’s still a little difficult to see at a glance what the string actually will look like: Where are the spaces? Do the parentheses in the string match up properly?

Option 3: absl::Substitute (google3/third_party/absl/strings/substitute.h)

However, an even better option exists: absl::Substitute(). Substitute() uses the same techniques as StrCat() to allow you to pass numeric values, std::strings, char*s, and string_views. It doesn’t require you to remember arcane format strings for numeric types (or string_view).

Even though everyone can more-or-less understand printf-style format strings, Substitute format strings are just as hard to misinterpret:

std::string GetErrorMessage(absl::string_view op, absl::string_view user,
                            int id) {
  return absl::Substitute("Error in $0 for user $1 ($2)", op, user, id);
}

Good readability, and better performance? LGTM.


Subscribe to the Abseil Blog