• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <algorithm>
20 #include <string>
21 #include <string_view>
22 
23 namespace android::mediautils {
24 
25 /*
26  * FixedString is a stack allocatable string buffer that supports
27  * simple appending of other strings and string_views.
28  *
29  * It is designed for no-malloc operation when std::string
30  * small buffer optimization is insufficient.
31  *
32  * To keep code small, use asStringView() for operations on this.
33  *
34  * Notes:
35  * 1) Appending beyond the internal buffer size results in truncation.
36  *
37  * Alternatives:
38  * 1) If you want a sharable copy-on-write string implementation,
39  *    consider using the legacy android::String8().
40  * 2) Using std::string with a fixed stack allocator may suit your needs,
41  *    but exception avoidance is tricky.
42  * 3) Using C++20 ranges https://en.cppreference.com/w/cpp/ranges if you don't
43  *    need backing store.  Be careful about allocation with ranges.
44  *
45  * Good small sizes are multiples of 16 minus 2, e.g. 14, 30, 46, 62.
46  *
47  * Implementation Notes:
48  * 1) No iterators or [] for FixedString - please convert to std::string_view.
49  * 2) For small N (e.g. less than 62), consider a change to always zero fill and
50  *    potentially prevent always zero terminating (if one only does append).
51  *
52  * Possible arguments to create/append:
53  * 1) A FixedString.
54  * 2) A single char.
55  * 3) A char * pointer.
56  * 4) A std::string.
57  * 5) A std::string_view (or something convertible to it).
58  *
59  * Example:
60  *
61  * FixedString s1(std::string("a"));    // ctor
62  * s1 << "bc" << 'd' << '\n';           // streaming append
63  * s1 += "hello";                       // += append
64  * ASSERT_EQ(s1, "abcd\nhello");
65  */
66 template <uint32_t N>
67 struct FixedString
68 {
69     // Find the best size type.
70     using strsize_t = std::conditional_t<(N > 255), uint32_t, uint8_t>;
71 
72     // constructors
FixedStringFixedString73     FixedString() { // override default
74         buffer_[0] = '\0';
75     }
76 
FixedStringFixedString77     FixedString(const FixedString& other) { // override default.
78         copyFrom<N>(other);
79     }
80 
81     // The following constructor is not explicit to allow
82     // FixedString<8> s = "abcd";
83     template <typename ...Types>
FixedStringFixedString84     FixedString(Types&&... args) {
85         append(std::forward<Types>(args)...);
86     }
87 
88     // copy assign (copyFrom checks for equality and returns *this).
89     FixedString& operator=(const FixedString& other) { // override default.
90         return copyFrom<N>(other);
91     }
92 
93     template <typename ...Types>
94     FixedString& operator=(Types&&... args) {
95         size_ = 0;
96         return append(std::forward<Types>(args)...);
97     }
98 
99     // operator equals
100     bool operator==(const char *s) const {
101         return strncmp(c_str(), s, capacity() + 1) == 0;
102     }
103 
104     bool operator==(const std::string_view s) const {
105         return size() == s.size() && memcmp(data(), s.data(), size()) == 0;
106     }
107 
108     template <uint32_t N_>
109     bool operator==(const FixedString<N_>& s) const {
110         return operator==(s.asStringView());
111     }
112 
113     // operator not-equals
114     template <typename T>
115     bool operator!=(const T& other) const {
116         return !operator==(other);
117     }
118 
119     // operator +=
120     template <typename ...Types>
121     FixedString& operator+=(Types&&... args) {
122         return append(std::forward<Types>(args)...);
123     }
124 
125     // conversion to std::string_view.
string_viewFixedString126     operator std::string_view() const {
127         return asStringView();
128     }
129 
130     // basic observers
buffer_offsetFixedString131     size_t buffer_offset() const { return offsetof(std::decay_t<decltype(*this)>, buffer_); }
capacityFixedString132     static constexpr uint32_t capacity() { return N; }
sizeFixedString133     uint32_t size() const { return size_; }
remainingFixedString134     uint32_t remaining() const { return size_ >= N ? 0 : N - size_; }
emptyFixedString135     bool empty() const { return size_ == 0; }
fullFixedString136     bool full() const { return size_ == N; }  // when full, possible truncation risk.
dataFixedString137     char * data() { return buffer_; }
dataFixedString138     const char * data() const { return buffer_; }
c_strFixedString139     const char * c_str() const { return buffer_; }
140 
asStringViewFixedString141     inline std::string_view asStringView() const {
142         return { buffer_, static_cast<size_t>(size_) };
143     }
asStringFixedString144     inline std::string asString() const {
145         return { buffer_, static_cast<size_t>(size_) };
146     }
147 
clearFixedString148     void clear() { size_ = 0; buffer_[0] = 0; }
149 
150     // Implementation of append - using templates
151     // to guarantee precedence in the presence of ambiguity.
152     //
153     // Consider C++20 template overloading through constraints and concepts.
154     template <typename T>
appendFixedString155     FixedString& append(const T& t) {
156         using decayT = std::decay_t<T>;
157         if constexpr (is_specialization_v<decayT, FixedString>) {
158             // A FixedString<U>
159             if (size_ == 0) {
160                 // optimization to erase everything.
161                 return copyFrom(t);
162             } else {
163                 return appendStringView({t.data(), t.size()});
164             }
165         } else if constexpr(std::is_same_v<decayT, char>) {
166             if (size_ < N) {
167                 buffer_[size_++] = t;
168                 buffer_[size_] = '\0';
169             }
170             return *this;
171         } else if constexpr(std::is_same_v<decayT, char *>) {
172             // Some char* ptr.
173             return appendString(t);
174         } else if constexpr (std::is_convertible_v<decayT, std::string_view>) {
175             // std::string_view, std::string, or some other convertible type.
176             return appendStringView(t);
177         } else /* constexpr */ {
178             static_assert(dependent_false_v<T>, "non-matching append type");
179         }
180     }
181 
appendStringViewFixedString182     FixedString& appendStringView(std::string_view s) {
183         uint32_t total = std::min(static_cast<size_t>(N - size_), s.size());
184         memcpy(buffer_ + size_, s.data(), total);
185         size_ += total;
186         buffer_[size_] = '\0';
187         return *this;
188     }
189 
appendStringFixedString190     FixedString& appendString(const char *s) {
191         // strncpy zero pads to the end,
192         // strlcpy returns total expected length,
193         // we don't have strncpy_s in Bionic,
194         // so we write our own here.
195         while (size_ < N && *s != '\0') {
196             buffer_[size_++] = *s++;
197         }
198         buffer_[size_] = '\0';
199         return *this;
200     }
201 
202     // Copy initialize the struct.
203     // Note: We are POD but customize the copying for acceleration
204     // of moving small strings embedded in a large buffers.
205     template <uint32_t U>
copyFromFixedString206     FixedString& copyFrom(const FixedString<U>& other) {
207         if ((void*)this != (void*)&other) { // not a self-assignment
208             if (other.size() == 0) {
209                 size_ = 0;
210                 buffer_[0] = '\0';
211                 return *this;
212             }
213             constexpr size_t kSizeToCopyWhole = 64;
214             if constexpr (N == U &&
215                     sizeof(*this) == sizeof(other) &&
216                     sizeof(*this) <= kSizeToCopyWhole) {
217                 // As we have the same str size type, we can just
218                 // memcpy with fixed size, which can be easily optimized.
219                 memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this));
220                 return *this;
221             }
222             if constexpr (std::is_same_v<strsize_t, typename FixedString<U>::strsize_t>) {
223                 constexpr size_t kAlign = 8;  // align to a multiple of 8.
224                 static_assert((kAlign & (kAlign - 1)) == 0); // power of 2.
225                 // we check any alignment issues.
226                 if (buffer_offset() == other.buffer_offset() && other.size() <= capacity()) {
227                     // improve on standard POD copying by reducing size.
228                     const size_t mincpy = buffer_offset() + other.size() + 1 /* nul */;
229                     const size_t maxcpy = std::min(sizeof(*this), sizeof(other));
230                     const size_t cpysize = std::min(mincpy + kAlign - 1 & ~(kAlign - 1), maxcpy);
231                     memcpy(static_cast<void*>(this), static_cast<const void*>(&other), cpysize);
232                     return *this;
233                 }
234             }
235             size_ = std::min(other.size(), capacity());
236             memcpy(buffer_, other.data(), size_);
237             buffer_[size_] = '\0';  // zero terminate.
238         }
239         return *this;
240     }
241 
242 private:
243     //  Template helper methods
244 
245     template <typename Test, template <uint32_t> class Ref>
246     struct is_specialization : std::false_type {};
247 
248     template <template <uint32_t> class Ref, uint32_t UU>
249     struct is_specialization<Ref<UU>, Ref>: std::true_type {};
250 
251     template <typename Test, template <uint32_t> class Ref>
252     static inline constexpr bool is_specialization_v = is_specialization<Test, Ref>::value;
253 
254     // For static assert(false) we need a template version to avoid early failure.
255     template <typename T>
256     static inline constexpr bool dependent_false_v = false;
257 
258     // POD variables
259     strsize_t size_ = 0;
260     char buffer_[N + 1 /* allow zero termination */];
261 };
262 
263 // Stream operator syntactic sugar.
264 // Example:
265 // s << 'b' << "c" << "d" << '\n';
266 template <uint32_t N, typename ...Types>
267 FixedString<N>& operator<<(FixedString<N>& fs, Types&&... args) {
268     return fs.append(std::forward<Types>(args)...);
269 }
270 
271 // We do not use a default size for fixed string as changing
272 // the default size would lead to different behavior - we want the
273 // size to be explicitly known.
274 
275 // FixedString62 of 62 chars fits in one typical cache line.
276 using FixedString62 = FixedString<62>;
277 
278 // Slightly smaller
279 using FixedString30 = FixedString<30>;
280 
281 // Since we have added copy and assignment optimizations,
282 // we are no longer trivially assignable and copyable.
283 // But we check standard layout here to prevent inclusion of unacceptable members or virtuals.
284 static_assert(std::is_standard_layout_v<FixedString62>);
285 static_assert(std::is_standard_layout_v<FixedString30>);
286 
287 }  // namespace android::mediautils
288