aboutsummaryrefslogtreecommitdiff
path: root/pw_string/guide.rst
blob: 87ceec03e591447b3833e43d588a027b71fa39d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
.. _module-pw_string-guide:

================
pw_string: Guide
================

InlineString and StringBuilder?
===============================
Use :cpp:type:`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 :cpp:class:`pw::StringBuilder` if you need:

* Compatibility with ``std::ostringstream``, including custom object support
* Storage 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 :cpp:type:`pw::InlineString` is wrapping a
length-delimited string (e.g. ``std::string_view``) for APIs that require null
termination:

.. code-block:: cpp

   #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 :cpp:class:`pw::StringBuilder` is when
constructing a string for external use.

.. code-block:: cpp

  #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 :cpp:class:`pw::StringBuilder`.

.. code-block:: cpp

  #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
======================================
:cpp:type:`pw::InlineString` objects must be constructed by specifying a fixed
capacity for the string.

.. code-block:: c++

   #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
=========================================================
:cpp:class:`pw::StringBuilder` can build a string in a
:cpp:type:`pw::InlineString`:

.. code-block:: c++

   #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
===================================
:cpp:type:`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 :cpp:type:`pw::InlineString` objects without knowing their type,
use the ``pw::InlineString<>`` type, shown in the examples below:

.. code-block:: c++

   // 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 :cpp:type:`pw::InlineString` objects don't work in
``constexpr`` contexts.

Known size strings
------------------
:cpp:type:`pw::InlineString` operations on known-size strings may be used in
``constexpr`` expressions.

.. code-block:: c++

   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
=======================================
:cpp:type:`pw::InlineBasicString` supports class template argument deduction
(CTAD) in C++17 and newer. Since :cpp:type:`pw::InlineString` is an alias, CTAD
is not supported until C++20.

.. code-block:: c++

   // 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:

.. code-block:: cpp

  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``:

.. code-block:: cpp

  #include "pw_string/to_string.h"

  namespace pw {

  template <>
  StatusWithSize ToString<MyStatus>(MyStatus value, span<char> buffer) {
    return Copy(MyStatusString(value), buffer);
  }

  }  // namespace pw