blob: e17c7b5d672b4327a9b5f098aafef37fb3a4ae0d [file] [log] [blame]
// Copyright 2022 The Pigweed Authors
//
// 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
//
// https://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 <cstddef>
#include <initializer_list>
#include <iterator>
#include "pw_assert/assert.h"
#include "pw_polyfill/standard.h"
#include "pw_preprocessor/compiler.h"
#include "pw_string/internal/string_impl.h"
// Messages to use in static_assert statements.
#define _PW_STRING_CAPACITY_TOO_SMALL_FOR_ARRAY \
"The pw::InlineString's capacity is too small to hold the assigned string " \
"literal or character array. When assigning a literal or array to a " \
"pw::InlineString, the pw::InlineString must be large enough for the " \
"entire string and an additional null terminator."
#define _PW_STRING_CAPACITY_TOO_SMALL_FOR_STRING \
"When assigning one pw::InlineString with known capacity to another, the " \
"capacity of the destination pw::InlineString must be at least as large as " \
"the source string."
namespace pw {
// pw::InlineBasicString<T, kCapacity> is a fixed-capacity version of
// std::basic_string. It implements mostly the same API as std::basic_string,
// but the capacity of the string is fixed at construction and cannot grow.
// Attempting to increase the size beyond the capacity triggers an assert.
//
// A pw::InlineString alias of pw::InlineBasicString<char>, equivalent to
// std::string, is defined below.
//
// pw::InlineBasicString has a template parameter for the capacity, but the
// capacity does not have to be known to use the string. The
// pw::InlineBasicString template inherits from a pw::InlineBasicString
// specialization with capacity of the reserved value pw::InlineString<>::npos.
// The actual capacity is stored in a single word alongside the size. This
// allows code to work with strings of any capacity through a pw::InlineString<>
// or pw::InlineBasicString<T> reference.
template <typename T, string_impl::size_type kCapacity = string_impl::kGeneric>
class InlineBasicString final
: public InlineBasicString<T, string_impl::kGeneric> {
public:
using typename InlineBasicString<T, string_impl::kGeneric>::value_type;
using typename InlineBasicString<T, string_impl::kGeneric>::size_type;
using typename InlineBasicString<T, string_impl::kGeneric>::difference_type;
using typename InlineBasicString<T, string_impl::kGeneric>::reference;
using typename InlineBasicString<T, string_impl::kGeneric>::const_reference;
using typename InlineBasicString<T, string_impl::kGeneric>::pointer;
using typename InlineBasicString<T, string_impl::kGeneric>::const_pointer;
using typename InlineBasicString<T, string_impl::kGeneric>::iterator;
using typename InlineBasicString<T, string_impl::kGeneric>::const_iterator;
using typename InlineBasicString<T, string_impl::kGeneric>::reverse_iterator;
using
typename InlineBasicString<T,
string_impl::kGeneric>::const_reverse_iterator;
using InlineBasicString<T, string_impl::kGeneric>::npos;
// Constructors
constexpr InlineBasicString() noexcept
: InlineBasicString<T, string_impl::kGeneric>(kCapacity), buffer_() {}
constexpr InlineBasicString(size_type count, T ch) : InlineBasicString() {
Fill(data(), ch, count);
}
template <size_type kOtherCapacity>
constexpr InlineBasicString(const InlineBasicString<T, kOtherCapacity>& other,
size_type index,
size_type count = npos)
: InlineBasicString() {
CopySubstr(data(), other.data(), other.size(), index, count);
}
constexpr InlineBasicString(const T* string, size_type count)
: InlineBasicString() {
Copy(data(), string, count);
}
template <typename U,
typename = string_impl::EnableIfNonArrayCharPointer<T, U>>
constexpr InlineBasicString(U c_string)
: InlineBasicString(
c_string, string_impl::BoundedStringLength(c_string, kCapacity)) {}
template <size_t kCharArraySize>
constexpr InlineBasicString(const T (&array)[kCharArraySize])
: InlineBasicString() {
static_assert(
kCharArraySize < string_impl::kGeneric && kCharArraySize <= kCapacity,
_PW_STRING_CAPACITY_TOO_SMALL_FOR_ARRAY);
Copy(data(), array, string_impl::ArrayStringLength(array));
}
template <typename InputIterator,
typename = string_impl::EnableIfInputIterator<InputIterator>>
constexpr InlineBasicString(InputIterator start, InputIterator finish)
: InlineBasicString() {
IteratorCopy(start, finish, data(), data() + max_size());
}
// Use the default copy for InlineBasicString with the same capacity.
constexpr InlineBasicString(const InlineBasicString&) = default;
// When copying from an InlineBasicString with a different capacity, check
// that the destination capacity is at least as large as the source capacity.
template <size_type kOtherCapacity>
constexpr InlineBasicString(const InlineBasicString<T, kOtherCapacity>& other)
: InlineBasicString(other.data(), other.size()) {
static_assert(
kOtherCapacity == string_impl::kGeneric || kOtherCapacity <= kCapacity,
_PW_STRING_CAPACITY_TOO_SMALL_FOR_STRING);
}
constexpr InlineBasicString(std::initializer_list<T> list)
: InlineBasicString(list.begin(), list.size()) {}
#if PW_CXX_STANDARD_IS_SUPPORTED(17) // std::string_view is a C++17 feature
// Unlike std::string, pw::InlineString<> supports implicit conversions from
// std::string_view. However, explicit conversions are still required from
// types that convert to std::string_view, as with std::string.
//
// pw::InlineString<> allows implicit conversions from std::string_view
// because it can be cumbersome to specify the capacity parameter. In
// particular, this can make using aggregate initialization more difficult.
//
// This explicit constructor is enabled for an argument that converts to
// std::string_view, but is not a std::string_view.
template <
typename StringViewLike,
string_impl::EnableIfStringViewLikeButNotStringView<T, StringViewLike>* =
nullptr>
explicit constexpr InlineBasicString(const StringViewLike& string)
: InlineBasicString(std::basic_string_view<T>(string)) {}
// This converting constructor is enabled for std::string_view, but not types
// that convert to it.
template <typename StringView,
std::enable_if_t<
std::is_same<StringView, std::basic_string_view<T>>::value>* =
nullptr>
constexpr InlineBasicString(const StringView& view)
: InlineBasicString(view.data(), view.size()) {}
template <typename StringView,
typename = string_impl::EnableIfStringViewLike<T, StringView>>
constexpr InlineBasicString(const StringView& string,
size_type index,
size_type count)
: InlineBasicString() {
const std::basic_string_view<T> view = string;
CopySubstr(data(), view.data(), view.size(), index, count);
}
#endif // PW_CXX_STANDARD_IS_SUPPORTED(17)
InlineBasicString(std::nullptr_t) = delete; // Cannot construct from nullptr
// Assignment operators
constexpr InlineBasicString& operator=(const InlineBasicString& other) =
default;
// Checks capacity rather than current size.
template <size_type kOtherCapacity>
constexpr InlineBasicString& operator=(
const InlineBasicString<T, kOtherCapacity>& other) {
return assign<kOtherCapacity>(other); // NOLINT
}
template <size_t kCharArraySize>
constexpr InlineBasicString& operator=(const T (&array)[kCharArraySize]) {
return assign<kCharArraySize>(array); // NOLINT
}
// Use SFINAE to avoid ambiguity with the array overload.
template <typename U,
typename = string_impl::EnableIfNonArrayCharPointer<T, U>>
constexpr InlineBasicString& operator=(U c_string) {
return assign(c_string); // NOLINT
}
constexpr InlineBasicString& operator=(T ch) {
static_assert(kCapacity != 0,
"Cannot assign a character to pw::InlineString<0>");
return assign(1, ch); // NOLINT
}
constexpr InlineBasicString& operator=(std::initializer_list<T> list) {
return assign(list); // NOLINT
}
#if PW_CXX_STANDARD_IS_SUPPORTED(17) // std::string_view is a C++17 feature
template <typename StringView,
typename = string_impl::EnableIfStringViewLike<T, StringView>>
constexpr InlineBasicString& operator=(const StringView& string) {
return assign(string); // NOLINT
}
#endif // PW_CXX_STANDARD_IS_SUPPORTED(17)
constexpr InlineBasicString& operator=(std::nullptr_t) = delete;
template <size_type kOtherCapacity>
constexpr InlineBasicString& operator+=(
const InlineBasicString<T, kOtherCapacity>& string) {
return append(string);
}
constexpr InlineBasicString& operator+=(T character) {
push_back(character);
return *this;
}
template <size_t kCharArraySize>
constexpr InlineBasicString& operator+=(const T (&array)[kCharArraySize]) {
return append(array);
}
template <typename U,
typename = string_impl::EnableIfNonArrayCharPointer<T, U>>
constexpr InlineBasicString& operator+=(U c_string) {
return append(c_string);
}
constexpr InlineBasicString& operator+=(std::initializer_list<T> list) {
return append(list.begin(), list.size());
}
#if PW_CXX_STANDARD_IS_SUPPORTED(17) // std::string_view is a C++17 feature
template <typename StringView,
typename = string_impl::EnableIfStringViewLike<T, StringView>>
constexpr InlineBasicString& operator+=(const StringView& string) {
return append(string);
}
#endif // PW_CXX_STANDARD_IS_SUPPORTED(17)
// The data() and size() functions are defined differently for the generic and
// known-size specializations. This is to support using pw::InlineBasicString
// in constexpr statements. This data() implementation simply returns the
// underlying buffer. The generic-capacity data() function casts *this to
// InlineBasicString<T, 0>, so is only constexpr when the capacity is actually
// 0.
constexpr pointer data() { return buffer_; }
constexpr const_pointer data() const { return buffer_; }
// Use the size() function from the base, but define max_size() to return the
// kCapacity template parameter instead of reading the stored capacity value.
using InlineBasicString<T, string_impl::kGeneric>::size;
constexpr size_type max_size() const noexcept { return kCapacity; }
// Most string functions are defined in separate file so they can be shared
// between the known capacity and generic capacity versions of
// InlineBasicString.
#include "pw_string/internal/string_common_functions.inc"
private:
using InlineBasicString<T, string_impl::kGeneric>::PushBack;
using InlineBasicString<T, string_impl::kGeneric>::PopBack;
using InlineBasicString<T, string_impl::kGeneric>::Copy;
using InlineBasicString<T, string_impl::kGeneric>::CopySubstr;
using InlineBasicString<T, string_impl::kGeneric>::Fill;
using InlineBasicString<T, string_impl::kGeneric>::IteratorCopy;
using InlineBasicString<T, string_impl::kGeneric>::CopyExtend;
using InlineBasicString<T, string_impl::kGeneric>::CopyExtendSubstr;
using InlineBasicString<T, string_impl::kGeneric>::FillExtend;
using InlineBasicString<T, string_impl::kGeneric>::IteratorExtend;
using InlineBasicString<T, string_impl::kGeneric>::Resize;
using InlineBasicString<T, string_impl::kGeneric>::SetSizeAndTerminate;
// Store kCapacity + 1 bytes to reserve space for a null terminator.
// InlineBasicString<T, 0> only stores a null terminator.
T buffer_[kCapacity + 1];
};
// Generic-capacity version of pw::InlineBasicString. Generic-capacity strings
// cannot be constructed; they can only be used as references to fixed-capacity
// pw::InlineBasicString objects.
template <typename T>
class InlineBasicString<T, string_impl::kGeneric> {
public:
using value_type = T;
using size_type = string_impl::size_type;
using difference_type = std::ptrdiff_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using iterator = value_type*;
using const_iterator = const value_type*;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
static constexpr size_type npos = string_impl::kGeneric;
InlineBasicString() = delete; // Must specify capacity to construct a string.
// For the generic-capacity pw::InlineBasicString, cast this object to a
// fixed-capacity class so the address of the data can be found. Even though
// the capacity isn't known at compile time, the location of the data never
// changes.
constexpr pointer data() noexcept {
return static_cast<InlineBasicString<T, 0>*>(this)->data();
}
constexpr const_pointer data() const noexcept {
return static_cast<const InlineBasicString<T, 0>*>(this)->data();
}
constexpr size_type size() const noexcept { return length_; }
constexpr size_type max_size() const noexcept { return capacity_; }
// Most string functions are defined in separate file so they can be shared
// between the known capacity and generic capacity versions of
// InlineBasicString.
#include "pw_string/internal/string_common_functions.inc"
protected:
explicit constexpr InlineBasicString(size_type capacity)
: capacity_(capacity), length_(0) {}
// The generic-capacity InlineBasicString<T> is not copyable or movable, but
// BasicStrings can copied or assigned through a fixed capacity derived class.
InlineBasicString(const InlineBasicString&) = default;
InlineBasicString& operator=(const InlineBasicString&) = default;
constexpr void PushBack(T* data, T ch);
constexpr void PopBack(T* data) {
PW_ASSERT(!empty());
SetSizeAndTerminate(data, size() - 1);
}
constexpr InlineBasicString& Copy(T* data,
const T* source,
size_type new_size);
constexpr InlineBasicString& CopySubstr(T* data,
const T* source,
size_type source_size,
size_type index,
size_type count);
constexpr InlineBasicString& Fill(T* data, T fill_char, size_type new_size);
template <typename InputIterator>
constexpr InlineBasicString& IteratorCopy(InputIterator start,
InputIterator finish,
T* data_start,
const T* data_finish) {
set_size(string_impl::IteratorCopyAndTerminate(
start, finish, data_start, data_finish));
return *this;
}
constexpr InlineBasicString& CopyExtend(T* data,
const T* source,
size_type count);
constexpr InlineBasicString& CopyExtendSubstr(T* data,
const T* source,
size_type source_size,
size_type index,
size_type count);
constexpr InlineBasicString& FillExtend(T* data,
T fill_char,
size_type count);
template <typename InputIterator>
constexpr InlineBasicString& IteratorExtend(InputIterator start,
InputIterator finish,
T* data_start,
const T* data_finish) {
length_ += string_impl::IteratorCopyAndTerminate(
start, finish, data_start, data_finish);
return *this;
}
constexpr void Resize(T* data, size_type new_size, T ch);
constexpr void set_size(size_type length) { length_ = length; }
constexpr void SetSizeAndTerminate(T* data, size_type length) {
string_impl::char_traits<T>::assign(data[length], T());
set_size(length);
}
private:
// Provide this constant for static_assert checks. If the capacity is unknown,
// use the maximum value so that compile-time capacity checks pass. If
// overflow occurs, the operation triggers a PW_ASSERT at runtime.
static constexpr size_type kCapacity = string_impl::kGeneric;
size_type capacity_;
size_type length_;
};
// Class template argument deduction guides
#ifdef __cpp_deduction_guides
// In C++17, the capacity of the string may be deduced from a string literal or
// array. For example, the following deduces a character type of char and a
// capacity of 5 (which includes the null terminator):
//
// InlineBasicString my_string = "1234";
//
// In C++20, the template parameters for the pw::InlineString alias may be
// deduced similarly:
//
// InlineString my_string = "abc"; // deduces capacity of 4.
//
template <typename T, size_t kCharArraySize>
InlineBasicString(const T (&)[kCharArraySize])
-> InlineBasicString<T, kCharArraySize>;
#endif // __cpp_deduction_guides
// Operators
// TODO(b/239996007): Implement operator+
template <typename T,
string_impl::size_type kLhsCapacity,
string_impl::size_type kRhsCapacity>
constexpr bool operator==(
const InlineBasicString<T, kLhsCapacity>& lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) noexcept {
return lhs.compare(rhs) == 0;
}
template <typename T,
string_impl::size_type kLhsCapacity,
string_impl::size_type kRhsCapacity>
constexpr bool operator!=(
const InlineBasicString<T, kLhsCapacity>& lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) noexcept {
return lhs.compare(rhs) != 0;
}
template <typename T,
string_impl::size_type kLhsCapacity,
string_impl::size_type kRhsCapacity>
constexpr bool operator<(
const InlineBasicString<T, kLhsCapacity>& lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) noexcept {
return lhs.compare(rhs) < 0;
}
template <typename T,
string_impl::size_type kLhsCapacity,
string_impl::size_type kRhsCapacity>
constexpr bool operator<=(
const InlineBasicString<T, kLhsCapacity>& lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) noexcept {
return lhs.compare(rhs) <= 0;
}
template <typename T,
string_impl::size_type kLhsCapacity,
string_impl::size_type kRhsCapacity>
constexpr bool operator>(
const InlineBasicString<T, kLhsCapacity>& lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) noexcept {
return lhs.compare(rhs) > 0;
}
template <typename T,
string_impl::size_type kLhsCapacity,
string_impl::size_type kRhsCapacity>
constexpr bool operator>=(
const InlineBasicString<T, kLhsCapacity>& lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) noexcept {
return lhs.compare(rhs) >= 0;
}
template <typename T, string_impl::size_type kLhsCapacity>
constexpr bool operator==(const InlineBasicString<T, kLhsCapacity>& lhs,
const T* rhs) {
return lhs.compare(rhs) == 0;
}
template <typename T, string_impl::size_type kRhsCapacity>
constexpr bool operator==(const T* lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) {
return rhs.compare(lhs) == 0;
}
template <typename T, string_impl::size_type kLhsCapacity>
constexpr bool operator!=(const InlineBasicString<T, kLhsCapacity>& lhs,
const T* rhs) {
return lhs.compare(rhs) != 0;
}
template <typename T, string_impl::size_type kRhsCapacity>
constexpr bool operator!=(const T* lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) {
return rhs.compare(lhs) != 0;
}
template <typename T, string_impl::size_type kLhsCapacity>
constexpr bool operator<(const InlineBasicString<T, kLhsCapacity>& lhs,
const T* rhs) {
return lhs.compare(rhs) < 0;
}
template <typename T, string_impl::size_type kRhsCapacity>
constexpr bool operator<(const T* lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) {
return rhs.compare(lhs) >= 0;
}
template <typename T, string_impl::size_type kLhsCapacity>
constexpr bool operator<=(const InlineBasicString<T, kLhsCapacity>& lhs,
const T* rhs) {
return lhs.compare(rhs) <= 0;
}
template <typename T, string_impl::size_type kRhsCapacity>
constexpr bool operator<=(const T* lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) {
return rhs.compare(lhs) >= 0;
}
template <typename T, string_impl::size_type kLhsCapacity>
constexpr bool operator>(const InlineBasicString<T, kLhsCapacity>& lhs,
const T* rhs) {
return lhs.compare(rhs) > 0;
}
template <typename T, string_impl::size_type kRhsCapacity>
constexpr bool operator>(const T* lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) {
return rhs.compare(lhs) <= 0;
}
template <typename T, string_impl::size_type kLhsCapacity>
constexpr bool operator>=(const InlineBasicString<T, kLhsCapacity>& lhs,
const T* rhs) {
return lhs.compare(rhs) >= 0;
}
template <typename T, string_impl::size_type kRhsCapacity>
constexpr bool operator>=(const T* lhs,
const InlineBasicString<T, kRhsCapacity>& rhs) {
return rhs.compare(lhs) <= 0;
}
// TODO(b/239996007): Implement other comparison operator overloads.
// Aliases
template <string_impl::size_type kCapacity = string_impl::kGeneric>
using InlineString = InlineBasicString<char, kCapacity>;
// Function implementations
template <typename T>
constexpr void InlineBasicString<T, string_impl::kGeneric>::PushBack(T* data,
T ch) {
PW_ASSERT(size() < max_size());
string_impl::char_traits<T>::assign(data[size()], ch);
SetSizeAndTerminate(data, size() + 1);
}
template <typename T>
constexpr InlineBasicString<T, string_impl::kGeneric>&
InlineBasicString<T, string_impl::kGeneric>::Copy(T* data,
const T* source,
size_type new_size) {
PW_ASSERT(new_size <= max_size());
string_impl::char_traits<T>::copy(data, source, new_size);
SetSizeAndTerminate(data, new_size);
return *this;
}
template <typename T>
constexpr InlineBasicString<T, string_impl::kGeneric>&
InlineBasicString<T, string_impl::kGeneric>::CopySubstr(T* data,
const T* source,
size_type source_size,
size_type index,
size_type count) {
PW_ASSERT(index <= source_size);
return Copy(data,
source + index,
std::min(count, static_cast<size_type>(source_size - index)));
}
template <typename T>
constexpr InlineBasicString<T, string_impl::kGeneric>&
InlineBasicString<T, string_impl::kGeneric>::Fill(T* data,
T fill_char,
size_type new_size) {
PW_ASSERT(new_size <= max_size());
string_impl::char_traits<T>::assign(data, new_size, fill_char);
SetSizeAndTerminate(data, new_size);
return *this;
}
template <typename T>
constexpr InlineBasicString<T, string_impl::kGeneric>&
InlineBasicString<T, string_impl::kGeneric>::CopyExtend(T* data,
const T* source,
size_type count) {
PW_ASSERT(count <= max_size() - size());
string_impl::char_traits<T>::copy(data + size(), source, count);
SetSizeAndTerminate(data, size() + count);
return *this;
}
template <typename T>
constexpr InlineBasicString<T, string_impl::kGeneric>&
InlineBasicString<T, string_impl::kGeneric>::CopyExtendSubstr(
T* data,
const T* source,
size_type source_size,
size_type index,
size_type count) {
PW_ASSERT(index <= source_size);
return CopyExtend(
data,
source + index,
std::min(count, static_cast<size_type>(source_size - index)));
return *this;
}
template <typename T>
constexpr InlineBasicString<T, string_impl::kGeneric>&
InlineBasicString<T, string_impl::kGeneric>::FillExtend(T* data,
T fill_char,
size_type count) {
PW_ASSERT(count <= max_size() - size());
string_impl::char_traits<T>::assign(data + size(), count, fill_char);
SetSizeAndTerminate(data, size() + count);
return *this;
}
template <typename T>
constexpr void InlineBasicString<T, string_impl::kGeneric>::Resize(
T* data, size_type new_size, T ch) {
PW_ASSERT(new_size <= max_size());
if (new_size > size()) {
string_impl::char_traits<T>::assign(data + size(), new_size - size(), ch);
}
SetSizeAndTerminate(data, new_size);
}
} // namespace pw
#undef _PW_STRING_CAPACITY_TOO_SMALL_FOR_ARRAY
#undef _PW_STRING_CAPACITY_TOO_SMALL_FOR_STRING