/* * 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 #include #include 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 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(other); } // The following constructor is not explicit to allow // FixedString<8> s = "abcd"; template FixedString(Types&&... args) { append(std::forward(args)...); } // copy assign (copyFrom checks for equality and returns *this). FixedString& operator=(const FixedString& other) { // override default. return copyFrom(other); } template FixedString& operator=(Types&&... args) { size_ = 0; return append(std::forward(args)...); } // operator equals bool operator==(const char *s) const { return strncmp(c_str(), s, capacity() + 1) == 0; } bool operator==(const std::string_view s) const { return size() == s.size() && memcmp(data(), s.data(), size()) == 0; } template bool operator==(const FixedString& s) const { return operator==(s.asStringView()); } // operator not-equals template bool operator!=(const T& other) const { return !operator==(other); } // operator += template FixedString& operator+=(Types&&... args) { return append(std::forward(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, 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_) }; } inline std::string asString() const { return { buffer_, static_cast(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 FixedString& append(const T& t) { using decayT = std::decay_t; if constexpr (is_specialization_v) { // A FixedString if (size_ == 0) { // optimization to erase everything. return copyFrom(t); } else { return appendStringView({t.data(), t.size()}); } } else if constexpr(std::is_same_v) { if (size_ < N) { buffer_[size_++] = t; buffer_[size_] = '\0'; } return *this; } else if constexpr(std::is_same_v) { // Some char* ptr. return appendString(t); } else if constexpr (std::is_convertible_v) { // std::string_view, std::string, or some other convertible type. return appendStringView(t); } else /* constexpr */ { static_assert(dependent_false_v, "non-matching append type"); } } FixedString& appendStringView(std::string_view s) { uint32_t total = std::min(static_cast(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 FixedString& copyFrom(const FixedString& 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(this), static_cast(&other), sizeof(*this)); return *this; } if constexpr (std::is_same_v::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(this), static_cast(&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 class Ref> struct is_specialization : std::false_type {}; template