| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #pragma once |
| |
| #include <algorithm> |
| #include <string> |
| #include <string_view> |
| |
| namespace android::mediautils { |
| |
| /* |
| * FixedString is a stack allocatable string buffer that supports |
| * simple appending of other strings and string_views. |
| * |
| * It is designed for no-malloc operation when std::string |
| * small buffer optimization is insufficient. |
| * |
| * To keep code small, use asStringView() for operations on this. |
| * |
| * Notes: |
| * 1) Appending beyond the internal buffer size results in truncation. |
| * |
| * Alternatives: |
| * 1) If you want a sharable copy-on-write string implementation, |
| * consider using the legacy android::String8(). |
| * 2) Using std::string with a fixed stack allocator may suit your needs, |
| * but exception avoidance is tricky. |
| * 3) Using C++20 ranges https://en.cppreference.com/w/cpp/ranges if you don't |
| * need backing store. Be careful about allocation with ranges. |
| * |
| * Good small sizes are multiples of 16 minus 2, e.g. 14, 30, 46, 62. |
| * |
| * Implementation Notes: |
| * 1) No iterators or [] for FixedString - please convert to std::string_view. |
| * 2) For small N (e.g. less than 62), consider a change to always zero fill and |
| * potentially prevent always zero terminating (if one only does append). |
| * |
| * Possible arguments to create/append: |
| * 1) A FixedString. |
| * 2) A single char. |
| * 3) A char * pointer. |
| * 4) A std::string. |
| * 5) A std::string_view (or something convertible to it). |
| * |
| * Example: |
| * |
| * FixedString s1(std::string("a")); // ctor |
| * s1 << "bc" << 'd' << '\n'; // streaming append |
| * s1 += "hello"; // += append |
| * ASSERT_EQ(s1, "abcd\nhello"); |
| */ |
| template <uint32_t N> |
| struct FixedString |
| { |
| // Find the best size type. |
| using strsize_t = std::conditional_t<(N > 255), uint32_t, uint8_t>; |
| |
| // constructors |
| FixedString() { // override default |
| buffer_[0] = '\0'; |
| } |
| |
| FixedString(const FixedString& other) { // override default. |
| copyFrom<N>(other); |
| } |
| |
| // The following constructor is not explicit to allow |
| // FixedString<8> s = "abcd"; |
| template <typename ...Types> |
| FixedString(Types&&... args) { |
| append(std::forward<Types>(args)...); |
| } |
| |
| // copy assign (copyFrom checks for equality and returns *this). |
| FixedString& operator=(const FixedString& other) { // override default. |
| return copyFrom<N>(other); |
| } |
| |
| template <typename ...Types> |
| FixedString& operator=(Types&&... args) { |
| size_ = 0; |
| return append(std::forward<Types>(args)...); |
| } |
| |
| // operator equals |
| bool operator==(const char *s) const { |
| return strncmp(c_str(), s, capacity() + 1) == 0; |
| } |
| |
| bool operator==(std::string_view s) const { |
| return size() == s.size() && memcmp(data(), s.data(), size()) == 0; |
| } |
| |
| // operator not-equals |
| template <typename T> |
| bool operator!=(const T& other) const { |
| return !operator==(other); |
| } |
| |
| // operator += |
| template <typename ...Types> |
| FixedString& operator+=(Types&&... args) { |
| return append(std::forward<Types>(args)...); |
| } |
| |
| // conversion to std::string_view. |
| operator std::string_view() const { |
| return asStringView(); |
| } |
| |
| // basic observers |
| size_t buffer_offset() const { return offsetof(std::decay_t<decltype(*this)>, buffer_); } |
| static constexpr uint32_t capacity() { return N; } |
| uint32_t size() const { return size_; } |
| uint32_t remaining() const { return size_ >= N ? 0 : N - size_; } |
| bool empty() const { return size_ == 0; } |
| bool full() const { return size_ == N; } // when full, possible truncation risk. |
| char * data() { return buffer_; } |
| const char * data() const { return buffer_; } |
| const char * c_str() const { return buffer_; } |
| |
| inline std::string_view asStringView() const { |
| return { buffer_, static_cast<size_t>(size_) }; |
| } |
| inline std::string asString() const { |
| return { buffer_, static_cast<size_t>(size_) }; |
| } |
| |
| void clear() { size_ = 0; buffer_[0] = 0; } |
| |
| // Implementation of append - using templates |
| // to guarantee precedence in the presence of ambiguity. |
| // |
| // Consider C++20 template overloading through constraints and concepts. |
| template <typename T> |
| FixedString& append(const T& t) { |
| using decayT = std::decay_t<T>; |
| if constexpr (is_specialization_v<decayT, FixedString>) { |
| // A FixedString<U> |
| if (size_ == 0) { |
| // optimization to erase everything. |
| return copyFrom(t); |
| } else { |
| return appendStringView({t.data(), t.size()}); |
| } |
| } else if constexpr(std::is_same_v<decayT, char>) { |
| if (size_ < N) { |
| buffer_[size_++] = t; |
| buffer_[size_] = '\0'; |
| } |
| return *this; |
| } else if constexpr(std::is_same_v<decayT, char *>) { |
| // Some char* ptr. |
| return appendString(t); |
| } else if constexpr (std::is_convertible_v<decayT, std::string_view>) { |
| // std::string_view, std::string, or some other convertible type. |
| return appendStringView(t); |
| } else /* constexpr */ { |
| static_assert(dependent_false_v<T>, "non-matching append type"); |
| } |
| } |
| |
| FixedString& appendStringView(std::string_view s) { |
| uint32_t total = std::min(static_cast<size_t>(N - size_), s.size()); |
| memcpy(buffer_ + size_, s.data(), total); |
| size_ += total; |
| buffer_[size_] = '\0'; |
| return *this; |
| } |
| |
| FixedString& appendString(const char *s) { |
| // strncpy zero pads to the end, |
| // strlcpy returns total expected length, |
| // we don't have strncpy_s in Bionic, |
| // so we write our own here. |
| while (size_ < N && *s != '\0') { |
| buffer_[size_++] = *s++; |
| } |
| buffer_[size_] = '\0'; |
| return *this; |
| } |
| |
| // Copy initialize the struct. |
| // Note: We are POD but customize the copying for acceleration |
| // of moving small strings embedded in a large buffers. |
| template <uint32_t U> |
| FixedString& copyFrom(const FixedString<U>& other) { |
| if ((void*)this != (void*)&other) { // not a self-assignment |
| if (other.size() == 0) { |
| size_ = 0; |
| buffer_[0] = '\0'; |
| return *this; |
| } |
| constexpr size_t kSizeToCopyWhole = 64; |
| if constexpr (N == U && |
| sizeof(*this) == sizeof(other) && |
| sizeof(*this) <= kSizeToCopyWhole) { |
| // As we have the same str size type, we can just |
| // memcpy with fixed size, which can be easily optimized. |
| memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this)); |
| return *this; |
| } |
| if constexpr (std::is_same_v<strsize_t, typename FixedString<U>::strsize_t>) { |
| constexpr size_t kAlign = 8; // align to a multiple of 8. |
| static_assert((kAlign & (kAlign - 1)) == 0); // power of 2. |
| // we check any alignment issues. |
| if (buffer_offset() == other.buffer_offset() && other.size() <= capacity()) { |
| // improve on standard POD copying by reducing size. |
| const size_t mincpy = buffer_offset() + other.size() + 1 /* nul */; |
| const size_t maxcpy = std::min(sizeof(*this), sizeof(other)); |
| const size_t cpysize = std::min(mincpy + kAlign - 1 & ~(kAlign - 1), maxcpy); |
| memcpy(static_cast<void*>(this), static_cast<const void*>(&other), cpysize); |
| return *this; |
| } |
| } |
| size_ = std::min(other.size(), capacity()); |
| memcpy(buffer_, other.data(), size_); |
| buffer_[size_] = '\0'; // zero terminate. |
| } |
| return *this; |
| } |
| |
| private: |
| // Template helper methods |
| |
| template <typename Test, template <uint32_t> class Ref> |
| struct is_specialization : std::false_type {}; |
| |
| template <template <uint32_t> class Ref, uint32_t UU> |
| struct is_specialization<Ref<UU>, Ref>: std::true_type {}; |
| |
| template <typename Test, template <uint32_t> class Ref> |
| static inline constexpr bool is_specialization_v = is_specialization<Test, Ref>::value; |
| |
| // For static assert(false) we need a template version to avoid early failure. |
| template <typename T> |
| static inline constexpr bool dependent_false_v = false; |
| |
| // POD variables |
| strsize_t size_ = 0; |
| char buffer_[N + 1 /* allow zero termination */]; |
| }; |
| |
| // Stream operator syntactic sugar. |
| // Example: |
| // s << 'b' << "c" << "d" << '\n'; |
| template <uint32_t N, typename ...Types> |
| FixedString<N>& operator<<(FixedString<N>& fs, Types&&... args) { |
| return fs.append(std::forward<Types>(args)...); |
| } |
| |
| // We do not use a default size for fixed string as changing |
| // the default size would lead to different behavior - we want the |
| // size to be explicitly known. |
| |
| // FixedString62 of 62 chars fits in one typical cache line. |
| using FixedString62 = FixedString<62>; |
| |
| // Slightly smaller |
| using FixedString30 = FixedString<30>; |
| |
| // Since we have added copy and assignment optimizations, |
| // we are no longer trivially assignable and copyable. |
| // But we check standard layout here to prevent inclusion of unacceptable members or virtuals. |
| static_assert(std::is_standard_layout_v<FixedString62>); |
| static_assert(std::is_standard_layout_v<FixedString30>); |
| |
| } // namespace android::mediautils |