pw_string: Guide#
Efficient, easy, and safe string manipulation
InlineString and StringBuilder?#
Use pw::InlineString
if you need:
Compatibility with
std::string
Storage internal to the object
A string object to persist in other data structures
Lower code size overhead
Use pw::StringBuilder
if you need:
Compatibility with
std::ostringstream
, including custom object supportStorage external to the object
Non-fatal handling of failed append/format operations
Tracking of the status of a series of operations
A temporary stack object to aid string construction
Medium code size overhead
An example of when to prefer pw::InlineString
is wrapping a
length-delimited string (e.g. std::string_view
) for APIs that require null
termination:
#include <string>
#include "pw_log/log.h"
#include "pw_string/string_builder.h"
void ProcessName(std::string_view name) {
// %s format strings require null terminated strings, so create one on the
// stack with size up to kMaxNameLen, copy the string view `name` contents
// into it, add a null terminator, and log it.
PW_LOG_DEBUG("The name is %s",
pw::InlineString<kMaxNameLen>(name).c_str());
}
An example of when to prefer pw::StringBuilder
is when
constructing a string for external use.
#include "pw_string/string_builder.h"
pw::Status FlushSensorValueToUart(int32_t sensor_value) {
pw::StringBuffer<42> sb;
sb << "Sensor value: ";
sb << sensor_value; // Formats as int.
FlushCStringToUart(sb.c_str());
if (!sb.status().ok) {
format_error_metric.Increment(); // Track overflows.
}
return sb.status();
}
Building strings with pw::StringBuilder#
The following shows basic use of a pw::StringBuilder
.
#include "pw_log/log.h"
#include "pw_string/string_builder.h"
pw::Status LogProducedData(std::string_view func_name,
span<const std::byte> data) {
// pw::StringBuffer allocates a pw::StringBuilder with a built-in buffer.
pw::StringBuffer<42> sb;
// Append a std::string_view to the buffer.
sb << func_name;
// Append a format string to the buffer.
sb.Format(" produced %d bytes of data: ", static_cast<int>(data.data()));
// Append bytes as hex to the buffer.
sb << data;
// Log the final string.
PW_LOG_DEBUG("%s", sb.c_str());
// Errors encountered while mutating the string builder are tracked.
return sb.status();
}
Building strings with pw::InlineString#
pw::InlineString
objects must be constructed by specifying a fixed
capacity for the string.
#include "pw_string/string.h"
// Initialize from a C string.
pw::InlineString<32> inline_string = "Literally";
inline_string.append('?', 3); // contains "Literally???"
// Supports copying into known-capacity strings.
pw::InlineString<64> other = inline_string;
// Supports various helpful std::string functions
if (inline_string.starts_with("Lit") || inline_string == "not\0literally"sv) {
other += inline_string;
}
// Like std::string, InlineString is always null terminated when accessed
// through c_str(). InlineString can be used to null-terminate
// length-delimited strings for APIs that expect null-terminated strings.
std::string_view file(".gif");
if (std::fopen(pw::InlineString<kMaxNameLen>(file).c_str(), "r") == nullptr) {
return;
}
// pw::InlineString integrates well with std::string_view. It supports
// implicit conversions to and from std::string_view.
inline_string = std::string_view("not\0literally", 12);
FunctionThatTakesAStringView(inline_string);
FunctionThatTakesAnInlineString(std::string_view("1234", 4));
Building strings inside InlineString with a StringBuilder#
pw::StringBuilder
can build a string in a
pw::InlineString
:
#include "pw_string/string.h"
void DoFoo() {
InlineString<32> inline_str;
StringBuilder sb(inline_str);
sb << 123 << "456";
// inline_str contains "456"
}
Passing InlineStrings as parameters#
pw::InlineString
objects can be passed to non-templated functions
via type erasure. This saves code size in most cases, since it avoids template
expansions triggered by string size differences.
Unknown size strings#
To operate on pw::InlineString
objects without knowing their type,
use the pw::InlineString<>
type, shown in the examples below:
// Note that the first argument is a generically-sized InlineString.
void RemoveSuffix(pw::InlineString<>& string, std::string_view suffix) {
if (string.ends_with(suffix)) {
string.resize(string.size() - suffix.size());
}
}
void DoStuff() {
pw::InlineString<32> str1 = "Good morning!";
RemoveSuffix(str1, " morning!");
pw::InlineString<40> str2 = "Good";
RemoveSuffix(str2, " morning!");
PW_ASSERT(str1 == str2);
}
However, generically sized pw::InlineString
objects don’t work in
constexpr
contexts.
Known size strings#
pw::InlineString
operations on known-size strings may be used in
constexpr
expressions.
static constexpr pw::InlineString<64> kMyString = [] {
pw::InlineString<64> string;
for (int i = 0; i < 10; ++i) {
string += "Hello";
}
return string;
}();
Compact initialization of InlineStrings#
pw::InlineBasicString
supports class template argument deduction
(CTAD) in C++17 and newer. Since pw::InlineString
is an alias, CTAD
is not supported until C++20.
// Deduces a capacity of 5 characters to match the 5-character string literal
// (not counting the null terminator).
pw::InlineBasicString inline_string = "12345";
// In C++20, CTAD may be used with the pw::InlineString alias.
pw::InlineString my_other_string("123456789");
Supporting custom types with StringBuilder#
As with std::ostream
, StringBuilder supports printing custom types by
overriding the <<
operator. This is is done by defining operator<<
in
the same namespace as the custom type. For example:
namespace my_project {
struct MyType {
int foo;
const char* bar;
};
pw::StringBuilder& operator<<(pw::StringBuilder& sb, const MyType& value) {
return sb << "MyType(" << value.foo << ", " << value.bar << ')';
}
} // namespace my_project
Internally, StringBuilder
uses the ToString
function to print. The
ToString
template function can be specialized to support custom types with
StringBuilder
, though it is recommended to overload operator<<
instead.
This example shows how to specialize pw::ToString
:
#include "pw_string/to_string.h"
namespace pw {
template <>
StatusWithSize ToString<MyStatus>(MyStatus value, span<char> buffer) {
return Copy(MyStatusString(value), buffer);
}
} // namespace pw
Saving code space by replacing snprintf
#
The C standard library function snprintf
is commonly used for string
formatting. However, it isn’t optimized for embedded systems, and using it will
bring in a lot of other standard library code that will inflate your binary
size.
Size comparison: snprintf versus pw::StringBuilder#
The fixed code size cost of pw::StringBuilder
is smaller than
that of std::snprintf
. Using only pw::StringBuilder
’s <<
and
append
methods instead of snprintf
leads to significant code size
reductions.
However, there are cases when the incremental code size cost of
pw::StringBuilder
is similar to that of snprintf
. For example,
each argument to pw::StringBuilder
’s <<
method expands to a
function call, but one or two pw::StringBuilder
appends may still
have a smaller code size impact than a single snprintf
call. Using
pw::StringBuilder
error handling will also impact code size in a
way that is comparable to snprintf
.
Label |
Segment |
Delta |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Total StringBuilder cost when used alongside snprintf |
FLASH
|
+1,568 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
StringBuilder cost when completely replacing snprintf |
FLASH
|
+656 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Incremental cost relative to snprintf for 10 strings |
FLASH
|
-24 |
Size comparison: snprintf versus pw::string::Format#
The pw::string::Format
functions have a small, fixed code size
cost. However, relative to equivalent std::snprintf
calls, there is no
incremental code size cost to using pw::string::Format
.
Label |
Segment |
Delta |
||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Format instead of snprintf once, return size |
FLASH
|
+88 |
||||||||||||||||||||||||
Format instead of snprintf 10 times, handle errors |
FLASH
|
+32 |
||||||||||||||||||||||||
Format instead of snprintf 50 times, no error handling |
FLASH
|
+96 |