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