vn-verdnaturachat/ios/Pods/Flipper-Folly/folly/FBString.h

2865 lines
83 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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.
*/
// @author: Andrei Alexandrescu (aalexandre)
// String type.
#pragma once
#include <atomic>
#include <cstddef>
#include <iosfwd>
#include <limits>
#include <stdexcept>
#include <type_traits>
#if FOLLY_HAS_STRING_VIEW
#include <string_view>
#endif
#include <folly/CPortability.h>
#include <folly/CppAttributes.h>
#include <folly/Likely.h>
#include <folly/Portability.h>
#include <algorithm>
#include <cassert>
#include <cstring>
#include <string>
#include <utility>
#include <folly/Traits.h>
#include <folly/hash/Hash.h>
#include <folly/lang/Assume.h>
#include <folly/lang/Exception.h>
#include <folly/memory/Malloc.h>
FOLLY_PUSH_WARNING
// Ignore shadowing warnings within this file, so includers can use -Wshadow.
FOLLY_GNU_DISABLE_WARNING("-Wshadow")
namespace folly {
// When compiling with ASan, always heap-allocate the string even if
// it would fit in-situ, so that ASan can detect access to the string
// buffer after it has been invalidated (destroyed, resized, etc.).
// Note that this flag doesn't remove support for in-situ strings, as
// that would break ABI-compatibility and wouldn't allow linking code
// compiled with this flag with code compiled without.
#ifdef FOLLY_SANITIZE_ADDRESS
#define FBSTRING_DISABLE_SSO true
#else
#define FBSTRING_DISABLE_SSO false
#endif
namespace fbstring_detail {
template <class InIt, class OutIt>
inline std::pair<InIt, OutIt> copy_n(
InIt b,
typename std::iterator_traits<InIt>::difference_type n,
OutIt d) {
for (; n != 0; --n, ++b, ++d) {
*d = *b;
}
return std::make_pair(b, d);
}
template <class Pod, class T>
inline void podFill(Pod* b, Pod* e, T c) {
assert(b && e && b <= e);
constexpr auto kUseMemset = sizeof(T) == 1;
if /* constexpr */ (kUseMemset) {
memset(b, c, size_t(e - b));
} else {
auto const ee = b + ((e - b) & ~7u);
for (; b != ee; b += 8) {
b[0] = c;
b[1] = c;
b[2] = c;
b[3] = c;
b[4] = c;
b[5] = c;
b[6] = c;
b[7] = c;
}
// Leftovers
for (; b != e; ++b) {
*b = c;
}
}
}
/*
* Lightly structured memcpy, simplifies copying PODs and introduces
* some asserts. Unfortunately using this function may cause
* measurable overhead (presumably because it adjusts from a begin/end
* convention to a pointer/size convention, so it does some extra
* arithmetic even though the caller might have done the inverse
* adaptation outside).
*/
template <class Pod>
inline void podCopy(const Pod* b, const Pod* e, Pod* d) {
assert(b != nullptr);
assert(e != nullptr);
assert(d != nullptr);
assert(e >= b);
assert(d >= e || d + (e - b) <= b);
memcpy(d, b, (e - b) * sizeof(Pod));
}
/*
* Lightly structured memmove, simplifies copying PODs and introduces
* some asserts
*/
template <class Pod>
inline void podMove(const Pod* b, const Pod* e, Pod* d) {
assert(e >= b);
memmove(d, b, (e - b) * sizeof(*b));
}
} // namespace fbstring_detail
/**
* Defines a special acquisition method for constructing fbstring
* objects. AcquireMallocatedString means that the user passes a
* pointer to a malloc-allocated string that the fbstring object will
* take into custody.
*/
enum class AcquireMallocatedString {};
/*
* fbstring_core_model is a mock-up type that defines all required
* signatures of a fbstring core. The fbstring class itself uses such
* a core object to implement all of the numerous member functions
* required by the standard.
*
* If you want to define a new core, copy the definition below and
* implement the primitives. Then plug the core into basic_fbstring as
* a template argument.
template <class Char>
class fbstring_core_model {
public:
fbstring_core_model();
fbstring_core_model(const fbstring_core_model &);
fbstring_core_model& operator=(const fbstring_core_model &) = delete;
~fbstring_core_model();
// Returns a pointer to string's buffer (currently only contiguous
// strings are supported). The pointer is guaranteed to be valid
// until the next call to a non-const member function.
const Char * data() const;
// Much like data(), except the string is prepared to support
// character-level changes. This call is a signal for
// e.g. reference-counted implementation to fork the data. The
// pointer is guaranteed to be valid until the next call to a
// non-const member function.
Char* mutableData();
// Returns a pointer to string's buffer and guarantees that a
// readable '\0' lies right after the buffer. The pointer is
// guaranteed to be valid until the next call to a non-const member
// function.
const Char * c_str() const;
// Shrinks the string by delta characters. Asserts that delta <=
// size().
void shrink(size_t delta);
// Expands the string by delta characters (i.e. after this call
// size() will report the old size() plus delta) but without
// initializing the expanded region. The expanded region is
// zero-terminated. Returns a pointer to the memory to be
// initialized (the beginning of the expanded portion). The caller
// is expected to fill the expanded area appropriately.
// If expGrowth is true, exponential growth is guaranteed.
// It is not guaranteed not to reallocate even if size() + delta <
// capacity(), so all references to the buffer are invalidated.
Char* expandNoinit(size_t delta, bool expGrowth);
// Expands the string by one character and sets the last character
// to c.
void push_back(Char c);
// Returns the string's size.
size_t size() const;
// Returns the string's capacity, i.e. maximum size that the string
// can grow to without reallocation. Note that for reference counted
// strings that's technically a lie - even assigning characters
// within the existing size would cause a reallocation.
size_t capacity() const;
// Returns true if the data underlying the string is actually shared
// across multiple strings (in a refcounted fashion).
bool isShared() const;
// Makes sure that at least minCapacity characters are available for
// the string without reallocation. For reference-counted strings,
// it should fork the data even if minCapacity < size().
void reserve(size_t minCapacity);
};
*/
/**
* This is the core of the string. The code should work on 32- and
* 64-bit and both big- and little-endianan architectures with any
* Char size.
*
* The storage is selected as follows (assuming we store one-byte
* characters on a 64-bit machine): (a) "small" strings between 0 and
* 23 chars are stored in-situ without allocation (the rightmost byte
* stores the size); (b) "medium" strings from 24 through 254 chars
* are stored in malloc-allocated memory that is copied eagerly; (c)
* "large" strings of 255 chars and above are stored in a similar
* structure as medium arrays, except that the string is
* reference-counted and copied lazily. the reference count is
* allocated right before the character array.
*
* The discriminator between these three strategies sits in two
* bits of the rightmost char of the storage:
* - If neither is set, then the string is small. Its length is represented by
* the lower-order bits on little-endian or the high-order bits on big-endian
* of that rightmost character. The value of these six bits is
* `maxSmallSize - size`, so this quantity must be subtracted from
* `maxSmallSize` to compute the `size` of the string (see `smallSize()`).
* This scheme ensures that when `size == `maxSmallSize`, the last byte in the
* storage is \0. This way, storage will be a null-terminated sequence of
* bytes, even if all 23 bytes of data are used on a 64-bit architecture.
* This enables `c_str()` and `data()` to simply return a pointer to the
* storage.
*
* - If the MSb is set, the string is medium width.
*
* - If the second MSb is set, then the string is large. On little-endian,
* these 2 bits are the 2 MSbs of MediumLarge::capacity_, while on
* big-endian, these 2 bits are the 2 LSbs. This keeps both little-endian
* and big-endian fbstring_core equivalent with merely different ops used
* to extract capacity/category.
*/
template <class Char>
class fbstring_core {
public:
fbstring_core() noexcept {
reset();
}
fbstring_core(const fbstring_core& rhs) {
assert(&rhs != this);
switch (rhs.category()) {
case Category::isSmall:
copySmall(rhs);
break;
case Category::isMedium:
copyMedium(rhs);
break;
case Category::isLarge:
copyLarge(rhs);
break;
default:
folly::assume_unreachable();
}
assert(size() == rhs.size());
assert(memcmp(data(), rhs.data(), size() * sizeof(Char)) == 0);
}
fbstring_core& operator=(const fbstring_core& rhs) = delete;
fbstring_core(fbstring_core&& goner) noexcept {
// Take goner's guts
ml_ = goner.ml_;
// Clean goner's carcass
goner.reset();
}
fbstring_core(
const Char* const data,
const size_t size,
bool disableSSO = FBSTRING_DISABLE_SSO) {
if (!disableSSO && size <= maxSmallSize) {
initSmall(data, size);
} else if (size <= maxMediumSize) {
initMedium(data, size);
} else {
initLarge(data, size);
}
assert(this->size() == size);
assert(size == 0 || memcmp(this->data(), data, size * sizeof(Char)) == 0);
}
~fbstring_core() noexcept {
if (category() == Category::isSmall) {
return;
}
destroyMediumLarge();
}
// Snatches a previously mallocated string. The parameter "size"
// is the size of the string, and the parameter "allocatedSize"
// is the size of the mallocated block. The string must be
// \0-terminated, so allocatedSize >= size + 1 and data[size] == '\0'.
//
// So if you want a 2-character string, pass malloc(3) as "data",
// pass 2 as "size", and pass 3 as "allocatedSize".
fbstring_core(
Char* const data,
const size_t size,
const size_t allocatedSize,
AcquireMallocatedString) {
if (size > 0) {
assert(allocatedSize >= size + 1);
assert(data[size] == '\0');
// Use the medium string storage
ml_.data_ = data;
ml_.size_ = size;
// Don't forget about null terminator
ml_.setCapacity(allocatedSize - 1, Category::isMedium);
} else {
// No need for the memory
free(data);
reset();
}
}
// swap below doesn't test whether &rhs == this (and instead
// potentially does extra work) on the premise that the rarity of
// that situation actually makes the check more expensive than is
// worth.
void swap(fbstring_core& rhs) {
auto const t = ml_;
ml_ = rhs.ml_;
rhs.ml_ = t;
}
// In C++11 data() and c_str() are 100% equivalent.
const Char* data() const {
return c_str();
}
Char* data() {
return c_str();
}
Char* mutableData() {
switch (category()) {
case Category::isSmall:
return small_;
case Category::isMedium:
return ml_.data_;
case Category::isLarge:
return mutableDataLarge();
}
folly::assume_unreachable();
}
const Char* c_str() const {
const Char* ptr = ml_.data_;
// With this syntax, GCC and Clang generate a CMOV instead of a branch.
ptr = (category() == Category::isSmall) ? small_ : ptr;
return ptr;
}
void shrink(const size_t delta) {
if (category() == Category::isSmall) {
shrinkSmall(delta);
} else if (
category() == Category::isMedium || RefCounted::refs(ml_.data_) == 1) {
shrinkMedium(delta);
} else {
shrinkLarge(delta);
}
}
FOLLY_NOINLINE
void reserve(size_t minCapacity, bool disableSSO = FBSTRING_DISABLE_SSO) {
switch (category()) {
case Category::isSmall:
reserveSmall(minCapacity, disableSSO);
break;
case Category::isMedium:
reserveMedium(minCapacity);
break;
case Category::isLarge:
reserveLarge(minCapacity);
break;
default:
folly::assume_unreachable();
}
assert(capacity() >= minCapacity);
}
Char* expandNoinit(
const size_t delta,
bool expGrowth = false,
bool disableSSO = FBSTRING_DISABLE_SSO);
void push_back(Char c) {
*expandNoinit(1, /* expGrowth = */ true) = c;
}
size_t size() const {
size_t ret = ml_.size_;
if /* constexpr */ (kIsLittleEndian) {
// We can save a couple instructions, because the category is
// small iff the last char, as unsigned, is <= maxSmallSize.
typedef typename std::make_unsigned<Char>::type UChar;
auto maybeSmallSize = size_t(maxSmallSize) -
size_t(static_cast<UChar>(small_[maxSmallSize]));
// With this syntax, GCC and Clang generate a CMOV instead of a branch.
ret = (static_cast<ssize_t>(maybeSmallSize) >= 0) ? maybeSmallSize : ret;
} else {
ret = (category() == Category::isSmall) ? smallSize() : ret;
}
return ret;
}
size_t capacity() const {
switch (category()) {
case Category::isSmall:
return maxSmallSize;
case Category::isLarge:
// For large-sized strings, a multi-referenced chunk has no
// available capacity. This is because any attempt to append
// data would trigger a new allocation.
if (RefCounted::refs(ml_.data_) > 1) {
return ml_.size_;
}
break;
case Category::isMedium:
default:
break;
}
return ml_.capacity();
}
bool isShared() const {
return category() == Category::isLarge && RefCounted::refs(ml_.data_) > 1;
}
private:
Char* c_str() {
Char* ptr = ml_.data_;
// With this syntax, GCC and Clang generate a CMOV instead of a branch.
ptr = (category() == Category::isSmall) ? small_ : ptr;
return ptr;
}
void reset() {
setSmallSize(0);
}
FOLLY_NOINLINE void destroyMediumLarge() noexcept {
auto const c = category();
assert(c != Category::isSmall);
if (c == Category::isMedium) {
free(ml_.data_);
} else {
RefCounted::decrementRefs(ml_.data_);
}
}
struct RefCounted {
std::atomic<size_t> refCount_;
Char data_[1];
constexpr static size_t getDataOffset() {
return offsetof(RefCounted, data_);
}
static RefCounted* fromData(Char* p) {
return static_cast<RefCounted*>(static_cast<void*>(
static_cast<unsigned char*>(static_cast<void*>(p)) -
getDataOffset()));
}
static size_t refs(Char* p) {
return fromData(p)->refCount_.load(std::memory_order_acquire);
}
static void incrementRefs(Char* p) {
fromData(p)->refCount_.fetch_add(1, std::memory_order_acq_rel);
}
static void decrementRefs(Char* p) {
auto const dis = fromData(p);
size_t oldcnt = dis->refCount_.fetch_sub(1, std::memory_order_acq_rel);
assert(oldcnt > 0);
if (oldcnt == 1) {
free(dis);
}
}
static RefCounted* create(size_t* size) {
const size_t allocSize =
goodMallocSize(getDataOffset() + (*size + 1) * sizeof(Char));
auto result = static_cast<RefCounted*>(checkedMalloc(allocSize));
result->refCount_.store(1, std::memory_order_release);
*size = (allocSize - getDataOffset()) / sizeof(Char) - 1;
return result;
}
static RefCounted* create(const Char* data, size_t* size) {
const size_t effectiveSize = *size;
auto result = create(size);
if (FOLLY_LIKELY(effectiveSize > 0)) {
fbstring_detail::podCopy(data, data + effectiveSize, result->data_);
}
return result;
}
static RefCounted* reallocate(
Char* const data,
const size_t currentSize,
const size_t currentCapacity,
size_t* newCapacity) {
assert(*newCapacity > 0 && *newCapacity > currentSize);
const size_t allocNewCapacity =
goodMallocSize(getDataOffset() + (*newCapacity + 1) * sizeof(Char));
auto const dis = fromData(data);
assert(dis->refCount_.load(std::memory_order_acquire) == 1);
auto result = static_cast<RefCounted*>(smartRealloc(
dis,
getDataOffset() + (currentSize + 1) * sizeof(Char),
getDataOffset() + (currentCapacity + 1) * sizeof(Char),
allocNewCapacity));
assert(result->refCount_.load(std::memory_order_acquire) == 1);
*newCapacity = (allocNewCapacity - getDataOffset()) / sizeof(Char) - 1;
return result;
}
};
typedef uint8_t category_type;
enum class Category : category_type {
isSmall = 0,
isMedium = kIsLittleEndian ? 0x80 : 0x2,
isLarge = kIsLittleEndian ? 0x40 : 0x1,
};
Category category() const {
// works for both big-endian and little-endian
return static_cast<Category>(bytes_[lastChar] & categoryExtractMask);
}
struct MediumLarge {
Char* data_;
size_t size_;
size_t capacity_;
size_t capacity() const {
return kIsLittleEndian ? capacity_ & capacityExtractMask : capacity_ >> 2;
}
void setCapacity(size_t cap, Category cat) {
capacity_ = kIsLittleEndian
? cap | (static_cast<size_t>(cat) << kCategoryShift)
: (cap << 2) | static_cast<size_t>(cat);
}
};
union {
uint8_t bytes_[sizeof(MediumLarge)]; // For accessing the last byte.
Char small_[sizeof(MediumLarge) / sizeof(Char)];
MediumLarge ml_;
};
constexpr static size_t lastChar = sizeof(MediumLarge) - 1;
constexpr static size_t maxSmallSize = lastChar / sizeof(Char);
constexpr static size_t maxMediumSize = 254 / sizeof(Char);
constexpr static uint8_t categoryExtractMask = kIsLittleEndian ? 0xC0 : 0x3;
constexpr static size_t kCategoryShift = (sizeof(size_t) - 1) * 8;
constexpr static size_t capacityExtractMask = kIsLittleEndian
? ~(size_t(categoryExtractMask) << kCategoryShift)
: 0x0 /* unused */;
static_assert(
!(sizeof(MediumLarge) % sizeof(Char)),
"Corrupt memory layout for fbstring.");
size_t smallSize() const {
assert(category() == Category::isSmall);
constexpr auto shift = kIsLittleEndian ? 0 : 2;
auto smallShifted = static_cast<size_t>(small_[maxSmallSize]) >> shift;
assert(static_cast<size_t>(maxSmallSize) >= smallShifted);
return static_cast<size_t>(maxSmallSize) - smallShifted;
}
void setSmallSize(size_t s) {
// Warning: this should work with uninitialized strings too,
// so don't assume anything about the previous value of
// small_[maxSmallSize].
assert(s <= maxSmallSize);
constexpr auto shift = kIsLittleEndian ? 0 : 2;
small_[maxSmallSize] = char((maxSmallSize - s) << shift);
small_[s] = '\0';
assert(category() == Category::isSmall && size() == s);
}
void copySmall(const fbstring_core&);
void copyMedium(const fbstring_core&);
void copyLarge(const fbstring_core&);
void initSmall(const Char* data, size_t size);
void initMedium(const Char* data, size_t size);
void initLarge(const Char* data, size_t size);
void reserveSmall(size_t minCapacity, bool disableSSO);
void reserveMedium(size_t minCapacity);
void reserveLarge(size_t minCapacity);
void shrinkSmall(size_t delta);
void shrinkMedium(size_t delta);
void shrinkLarge(size_t delta);
void unshare(size_t minCapacity = 0);
Char* mutableDataLarge();
};
template <class Char>
inline void fbstring_core<Char>::copySmall(const fbstring_core& rhs) {
static_assert(offsetof(MediumLarge, data_) == 0, "fbstring layout failure");
static_assert(
offsetof(MediumLarge, size_) == sizeof(ml_.data_),
"fbstring layout failure");
static_assert(
offsetof(MediumLarge, capacity_) == 2 * sizeof(ml_.data_),
"fbstring layout failure");
// Just write the whole thing, don't look at details. In
// particular we need to copy capacity anyway because we want
// to set the size (don't forget that the last character,
// which stores a short string's length, is shared with the
// ml_.capacity field).
ml_ = rhs.ml_;
assert(category() == Category::isSmall && this->size() == rhs.size());
}
template <class Char>
FOLLY_NOINLINE inline void fbstring_core<Char>::copyMedium(
const fbstring_core& rhs) {
// Medium strings are copied eagerly. Don't forget to allocate
// one extra Char for the null terminator.
auto const allocSize = goodMallocSize((1 + rhs.ml_.size_) * sizeof(Char));
ml_.data_ = static_cast<Char*>(checkedMalloc(allocSize));
// Also copies terminator.
fbstring_detail::podCopy(
rhs.ml_.data_, rhs.ml_.data_ + rhs.ml_.size_ + 1, ml_.data_);
ml_.size_ = rhs.ml_.size_;
ml_.setCapacity(allocSize / sizeof(Char) - 1, Category::isMedium);
assert(category() == Category::isMedium);
}
template <class Char>
FOLLY_NOINLINE inline void fbstring_core<Char>::copyLarge(
const fbstring_core& rhs) {
// Large strings are just refcounted
ml_ = rhs.ml_;
RefCounted::incrementRefs(ml_.data_);
assert(category() == Category::isLarge && size() == rhs.size());
}
// Small strings are bitblitted
template <class Char>
inline void fbstring_core<Char>::initSmall(
const Char* const data,
const size_t size) {
// Layout is: Char* data_, size_t size_, size_t capacity_
static_assert(
sizeof(*this) == sizeof(Char*) + 2 * sizeof(size_t),
"fbstring has unexpected size");
static_assert(
sizeof(Char*) == sizeof(size_t), "fbstring size assumption violation");
// sizeof(size_t) must be a power of 2
static_assert(
(sizeof(size_t) & (sizeof(size_t) - 1)) == 0,
"fbstring size assumption violation");
// If data is aligned, use fast word-wise copying. Otherwise,
// use conservative memcpy.
// The word-wise path reads bytes which are outside the range of
// the string, and makes ASan unhappy, so we disable it when
// compiling with ASan.
#ifndef FOLLY_SANITIZE_ADDRESS
if ((reinterpret_cast<size_t>(data) & (sizeof(size_t) - 1)) == 0) {
const size_t byteSize = size * sizeof(Char);
constexpr size_t wordWidth = sizeof(size_t);
switch ((byteSize + wordWidth - 1) / wordWidth) { // Number of words.
case 3:
ml_.capacity_ = reinterpret_cast<const size_t*>(data)[2];
FOLLY_FALLTHROUGH;
case 2:
ml_.size_ = reinterpret_cast<const size_t*>(data)[1];
FOLLY_FALLTHROUGH;
case 1:
ml_.data_ = *reinterpret_cast<Char**>(const_cast<Char*>(data));
FOLLY_FALLTHROUGH;
case 0:
break;
}
} else
#endif
{
if (size != 0) {
fbstring_detail::podCopy(data, data + size, small_);
}
}
setSmallSize(size);
}
template <class Char>
FOLLY_NOINLINE inline void fbstring_core<Char>::initMedium(
const Char* const data,
const size_t size) {
// Medium strings are allocated normally. Don't forget to
// allocate one extra Char for the terminating null.
auto const allocSize = goodMallocSize((1 + size) * sizeof(Char));
ml_.data_ = static_cast<Char*>(checkedMalloc(allocSize));
if (FOLLY_LIKELY(size > 0)) {
fbstring_detail::podCopy(data, data + size, ml_.data_);
}
ml_.size_ = size;
ml_.setCapacity(allocSize / sizeof(Char) - 1, Category::isMedium);
ml_.data_[size] = '\0';
}
template <class Char>
FOLLY_NOINLINE inline void fbstring_core<Char>::initLarge(
const Char* const data,
const size_t size) {
// Large strings are allocated differently
size_t effectiveCapacity = size;
auto const newRC = RefCounted::create(data, &effectiveCapacity);
ml_.data_ = newRC->data_;
ml_.size_ = size;
ml_.setCapacity(effectiveCapacity, Category::isLarge);
ml_.data_[size] = '\0';
}
template <class Char>
FOLLY_NOINLINE inline void fbstring_core<Char>::unshare(size_t minCapacity) {
assert(category() == Category::isLarge);
size_t effectiveCapacity = std::max(minCapacity, ml_.capacity());
auto const newRC = RefCounted::create(&effectiveCapacity);
// If this fails, someone placed the wrong capacity in an
// fbstring.
assert(effectiveCapacity >= ml_.capacity());
// Also copies terminator.
fbstring_detail::podCopy(ml_.data_, ml_.data_ + ml_.size_ + 1, newRC->data_);
RefCounted::decrementRefs(ml_.data_);
ml_.data_ = newRC->data_;
ml_.setCapacity(effectiveCapacity, Category::isLarge);
// size_ remains unchanged.
}
template <class Char>
inline Char* fbstring_core<Char>::mutableDataLarge() {
assert(category() == Category::isLarge);
if (RefCounted::refs(ml_.data_) > 1) { // Ensure unique.
unshare();
}
return ml_.data_;
}
template <class Char>
FOLLY_NOINLINE inline void fbstring_core<Char>::reserveLarge(
size_t minCapacity) {
assert(category() == Category::isLarge);
if (RefCounted::refs(ml_.data_) > 1) { // Ensure unique
// We must make it unique regardless; in-place reallocation is
// useless if the string is shared. In order to not surprise
// people, reserve the new block at current capacity or
// more. That way, a string's capacity never shrinks after a
// call to reserve.
unshare(minCapacity);
} else {
// String is not shared, so let's try to realloc (if needed)
if (minCapacity > ml_.capacity()) {
// Asking for more memory
auto const newRC = RefCounted::reallocate(
ml_.data_, ml_.size_, ml_.capacity(), &minCapacity);
ml_.data_ = newRC->data_;
ml_.setCapacity(minCapacity, Category::isLarge);
}
assert(capacity() >= minCapacity);
}
}
template <class Char>
FOLLY_NOINLINE inline void fbstring_core<Char>::reserveMedium(
const size_t minCapacity) {
assert(category() == Category::isMedium);
// String is not shared
if (minCapacity <= ml_.capacity()) {
return; // nothing to do, there's enough room
}
if (minCapacity <= maxMediumSize) {
// Keep the string at medium size. Don't forget to allocate
// one extra Char for the terminating null.
size_t capacityBytes = goodMallocSize((1 + minCapacity) * sizeof(Char));
// Also copies terminator.
ml_.data_ = static_cast<Char*>(smartRealloc(
ml_.data_,
(ml_.size_ + 1) * sizeof(Char),
(ml_.capacity() + 1) * sizeof(Char),
capacityBytes));
ml_.setCapacity(capacityBytes / sizeof(Char) - 1, Category::isMedium);
} else {
// Conversion from medium to large string
fbstring_core nascent;
// Will recurse to another branch of this function
nascent.reserve(minCapacity);
nascent.ml_.size_ = ml_.size_;
// Also copies terminator.
fbstring_detail::podCopy(
ml_.data_, ml_.data_ + ml_.size_ + 1, nascent.ml_.data_);
nascent.swap(*this);
assert(capacity() >= minCapacity);
}
}
template <class Char>
FOLLY_NOINLINE inline void fbstring_core<Char>::reserveSmall(
size_t minCapacity,
const bool disableSSO) {
assert(category() == Category::isSmall);
if (!disableSSO && minCapacity <= maxSmallSize) {
// small
// Nothing to do, everything stays put
} else if (minCapacity <= maxMediumSize) {
// medium
// Don't forget to allocate one extra Char for the terminating null
auto const allocSizeBytes =
goodMallocSize((1 + minCapacity) * sizeof(Char));
auto const pData = static_cast<Char*>(checkedMalloc(allocSizeBytes));
auto const size = smallSize();
// Also copies terminator.
fbstring_detail::podCopy(small_, small_ + size + 1, pData);
ml_.data_ = pData;
ml_.size_ = size;
ml_.setCapacity(allocSizeBytes / sizeof(Char) - 1, Category::isMedium);
} else {
// large
auto const newRC = RefCounted::create(&minCapacity);
auto const size = smallSize();
// Also copies terminator.
fbstring_detail::podCopy(small_, small_ + size + 1, newRC->data_);
ml_.data_ = newRC->data_;
ml_.size_ = size;
ml_.setCapacity(minCapacity, Category::isLarge);
assert(capacity() >= minCapacity);
}
}
template <class Char>
inline Char* fbstring_core<Char>::expandNoinit(
const size_t delta,
bool expGrowth, /* = false */
bool disableSSO /* = FBSTRING_DISABLE_SSO */) {
// Strategy is simple: make room, then change size
assert(capacity() >= size());
size_t sz, newSz;
if (category() == Category::isSmall) {
sz = smallSize();
newSz = sz + delta;
if (!disableSSO && FOLLY_LIKELY(newSz <= maxSmallSize)) {
setSmallSize(newSz);
return small_ + sz;
}
reserveSmall(
expGrowth ? std::max(newSz, 2 * maxSmallSize) : newSz, disableSSO);
} else {
sz = ml_.size_;
newSz = sz + delta;
if (FOLLY_UNLIKELY(newSz > capacity())) {
// ensures not shared
reserve(expGrowth ? std::max(newSz, 1 + capacity() * 3 / 2) : newSz);
}
}
assert(capacity() >= newSz);
// Category can't be small - we took care of that above
assert(category() == Category::isMedium || category() == Category::isLarge);
ml_.size_ = newSz;
ml_.data_[newSz] = '\0';
assert(size() == newSz);
return ml_.data_ + sz;
}
template <class Char>
inline void fbstring_core<Char>::shrinkSmall(const size_t delta) {
// Check for underflow
assert(delta <= smallSize());
setSmallSize(smallSize() - delta);
}
template <class Char>
inline void fbstring_core<Char>::shrinkMedium(const size_t delta) {
// Medium strings and unique large strings need no special
// handling.
assert(ml_.size_ >= delta);
ml_.size_ -= delta;
ml_.data_[ml_.size_] = '\0';
}
template <class Char>
inline void fbstring_core<Char>::shrinkLarge(const size_t delta) {
assert(ml_.size_ >= delta);
// Shared large string, must make unique. This is because of the
// durn terminator must be written, which may trample the shared
// data.
if (delta) {
fbstring_core(ml_.data_, ml_.size_ - delta).swap(*this);
}
// No need to write the terminator.
}
/**
* Dummy fbstring core that uses an actual std::string. This doesn't
* make any sense - it's just for testing purposes.
*/
template <class Char>
class dummy_fbstring_core {
public:
dummy_fbstring_core() {}
dummy_fbstring_core(const dummy_fbstring_core& another)
: backend_(another.backend_) {}
dummy_fbstring_core(const Char* s, size_t n) : backend_(s, n) {}
void swap(dummy_fbstring_core& rhs) {
backend_.swap(rhs.backend_);
}
const Char* data() const {
return backend_.data();
}
Char* mutableData() {
return const_cast<Char*>(backend_.data());
}
void shrink(size_t delta) {
assert(delta <= size());
backend_.resize(size() - delta);
}
Char* expandNoinit(size_t delta) {
auto const sz = size();
backend_.resize(size() + delta);
return backend_.data() + sz;
}
void push_back(Char c) {
backend_.push_back(c);
}
size_t size() const {
return backend_.size();
}
size_t capacity() const {
return backend_.capacity();
}
bool isShared() const {
return false;
}
void reserve(size_t minCapacity) {
backend_.reserve(minCapacity);
}
private:
std::basic_string<Char> backend_;
};
/**
* This is the basic_string replacement. For conformity,
* basic_fbstring takes the same template parameters, plus the last
* one which is the core.
*/
template <
typename E,
class T = std::char_traits<E>,
class A = std::allocator<E>,
class Storage = fbstring_core<E>>
class basic_fbstring {
template <typename Ex, typename... Args>
FOLLY_ALWAYS_INLINE static void enforce(bool condition, Args&&... args) {
if (!condition) {
throw_exception<Ex>(static_cast<Args&&>(args)...);
}
}
bool isSane() const {
return begin() <= end() && empty() == (size() == 0) &&
empty() == (begin() == end()) && size() <= max_size() &&
capacity() <= max_size() && size() <= capacity() &&
begin()[size()] == '\0';
}
struct Invariant {
Invariant& operator=(const Invariant&) = delete;
explicit Invariant(const basic_fbstring& s) noexcept : s_(s) {
assert(s_.isSane());
}
~Invariant() noexcept {
assert(s_.isSane());
}
private:
const basic_fbstring& s_;
};
public:
// types
typedef T traits_type;
typedef typename traits_type::char_type value_type;
typedef A allocator_type;
typedef typename std::allocator_traits<A>::size_type size_type;
typedef typename std::allocator_traits<A>::difference_type difference_type;
typedef typename std::allocator_traits<A>::value_type& reference;
typedef typename std::allocator_traits<A>::value_type const& const_reference;
typedef typename std::allocator_traits<A>::pointer pointer;
typedef typename std::allocator_traits<A>::const_pointer const_pointer;
typedef E* iterator;
typedef const E* const_iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
static constexpr size_type npos = size_type(-1);
typedef std::true_type IsRelocatable;
private:
static void procrustes(size_type& n, size_type nmax) {
if (n > nmax) {
n = nmax;
}
}
static size_type traitsLength(const value_type* s);
public:
// C++11 21.4.2 construct/copy/destroy
// Note: while the following two constructors can be (and previously were)
// collapsed into one constructor written this way:
//
// explicit basic_fbstring(const A& a = A()) noexcept { }
//
// This can cause Clang (at least version 3.7) to fail with the error:
// "chosen constructor is explicit in copy-initialization ...
// in implicit initialization of field '(x)' with omitted initializer"
//
// if used in a struct which is default-initialized. Hence the split into
// these two separate constructors.
basic_fbstring() noexcept : basic_fbstring(A()) {}
explicit basic_fbstring(const A&) noexcept {}
basic_fbstring(const basic_fbstring& str) : store_(str.store_) {}
// Move constructor
basic_fbstring(basic_fbstring&& goner) noexcept
: store_(std::move(goner.store_)) {}
// This is defined for compatibility with std::string
template <typename A2>
/* implicit */ basic_fbstring(const std::basic_string<E, T, A2>& str)
: store_(str.data(), str.size()) {}
basic_fbstring(
const basic_fbstring& str,
size_type pos,
size_type n = npos,
const A& /* a */ = A()) {
assign(str, pos, n);
}
FOLLY_NOINLINE
/* implicit */ basic_fbstring(const value_type* s, const A& /*a*/ = A())
: store_(s, traitsLength(s)) {}
FOLLY_NOINLINE
basic_fbstring(const value_type* s, size_type n, const A& /*a*/ = A())
: store_(s, n) {}
FOLLY_NOINLINE
basic_fbstring(size_type n, value_type c, const A& /*a*/ = A()) {
auto const pData = store_.expandNoinit(n);
fbstring_detail::podFill(pData, pData + n, c);
}
template <class InIt>
FOLLY_NOINLINE basic_fbstring(
InIt begin,
InIt end,
typename std::enable_if<
!std::is_same<InIt, value_type*>::value,
const A>::type& /*a*/
= A()) {
assign(begin, end);
}
// Specialization for const char*, const char*
FOLLY_NOINLINE
basic_fbstring(const value_type* b, const value_type* e, const A& /*a*/ = A())
: store_(b, size_type(e - b)) {}
// Nonstandard constructor
basic_fbstring(
value_type* s,
size_type n,
size_type c,
AcquireMallocatedString a)
: store_(s, n, c, a) {}
// Construction from initialization list
FOLLY_NOINLINE
basic_fbstring(std::initializer_list<value_type> il) {
assign(il.begin(), il.end());
}
~basic_fbstring() noexcept {}
basic_fbstring& operator=(const basic_fbstring& lhs);
// Move assignment
basic_fbstring& operator=(basic_fbstring&& goner) noexcept;
// Compatibility with std::string
template <typename A2>
basic_fbstring& operator=(const std::basic_string<E, T, A2>& rhs) {
return assign(rhs.data(), rhs.size());
}
// Compatibility with std::string
std::basic_string<E, T, A> toStdString() const {
return std::basic_string<E, T, A>(data(), size());
}
basic_fbstring& operator=(const value_type* s) {
return assign(s);
}
basic_fbstring& operator=(value_type c);
// This actually goes directly against the C++ spec, but the
// value_type overload is dangerous, so we're explicitly deleting
// any overloads of operator= that could implicitly convert to
// value_type.
// Note that we do need to explicitly specify the template types because
// otherwise MSVC 2017 will aggressively pre-resolve value_type to
// traits_type::char_type, which won't compare as equal when determining
// which overload the implementation is referring to.
template <typename TP>
typename std::enable_if<
std::is_convertible<
TP,
typename basic_fbstring<E, T, A, Storage>::value_type>::value &&
!std::is_same<
typename std::decay<TP>::type,
typename basic_fbstring<E, T, A, Storage>::value_type>::value,
basic_fbstring<E, T, A, Storage>&>::type
operator=(TP c) = delete;
basic_fbstring& operator=(std::initializer_list<value_type> il) {
return assign(il.begin(), il.end());
}
#if FOLLY_HAS_STRING_VIEW
operator std::basic_string_view<value_type, traits_type>() const noexcept {
return {data(), size()};
}
#endif
// C++11 21.4.3 iterators:
iterator begin() {
return store_.mutableData();
}
const_iterator begin() const {
return store_.data();
}
const_iterator cbegin() const {
return begin();
}
iterator end() {
return store_.mutableData() + store_.size();
}
const_iterator end() const {
return store_.data() + store_.size();
}
const_iterator cend() const {
return end();
}
reverse_iterator rbegin() {
return reverse_iterator(end());
}
const_reverse_iterator rbegin() const {
return const_reverse_iterator(end());
}
const_reverse_iterator crbegin() const {
return rbegin();
}
reverse_iterator rend() {
return reverse_iterator(begin());
}
const_reverse_iterator rend() const {
return const_reverse_iterator(begin());
}
const_reverse_iterator crend() const {
return rend();
}
// Added by C++11
// C++11 21.4.5, element access:
const value_type& front() const {
return *begin();
}
const value_type& back() const {
assert(!empty());
// Should be begin()[size() - 1], but that branches twice
return *(end() - 1);
}
value_type& front() {
return *begin();
}
value_type& back() {
assert(!empty());
// Should be begin()[size() - 1], but that branches twice
return *(end() - 1);
}
void pop_back() {
assert(!empty());
store_.shrink(1);
}
// C++11 21.4.4 capacity:
size_type size() const {
return store_.size();
}
size_type length() const {
return size();
}
size_type max_size() const {
return std::numeric_limits<size_type>::max();
}
void resize(size_type n, value_type c = value_type());
size_type capacity() const {
return store_.capacity();
}
void reserve(size_type res_arg = 0) {
enforce<std::length_error>(res_arg <= max_size(), "");
store_.reserve(res_arg);
}
void shrink_to_fit() {
// Shrink only if slack memory is sufficiently large
if (capacity() < size() * 3 / 2) {
return;
}
basic_fbstring(cbegin(), cend()).swap(*this);
}
void clear() {
resize(0);
}
bool empty() const {
return size() == 0;
}
// C++11 21.4.5 element access:
const_reference operator[](size_type pos) const {
return *(begin() + pos);
}
reference operator[](size_type pos) {
return *(begin() + pos);
}
const_reference at(size_type n) const {
enforce<std::out_of_range>(n < size(), "");
return (*this)[n];
}
reference at(size_type n) {
enforce<std::out_of_range>(n < size(), "");
return (*this)[n];
}
// C++11 21.4.6 modifiers:
basic_fbstring& operator+=(const basic_fbstring& str) {
return append(str);
}
basic_fbstring& operator+=(const value_type* s) {
return append(s);
}
basic_fbstring& operator+=(const value_type c) {
push_back(c);
return *this;
}
basic_fbstring& operator+=(std::initializer_list<value_type> il) {
append(il);
return *this;
}
basic_fbstring& append(const basic_fbstring& str);
basic_fbstring&
append(const basic_fbstring& str, const size_type pos, size_type n);
basic_fbstring& append(const value_type* s, size_type n);
basic_fbstring& append(const value_type* s) {
return append(s, traitsLength(s));
}
basic_fbstring& append(size_type n, value_type c);
template <class InputIterator>
basic_fbstring& append(InputIterator first, InputIterator last) {
insert(end(), first, last);
return *this;
}
basic_fbstring& append(std::initializer_list<value_type> il) {
return append(il.begin(), il.end());
}
void push_back(const value_type c) { // primitive
store_.push_back(c);
}
basic_fbstring& assign(const basic_fbstring& str) {
if (&str == this) {
return *this;
}
return assign(str.data(), str.size());
}
basic_fbstring& assign(basic_fbstring&& str) {
return *this = std::move(str);
}
basic_fbstring&
assign(const basic_fbstring& str, const size_type pos, size_type n);
basic_fbstring& assign(const value_type* s, const size_type n);
basic_fbstring& assign(const value_type* s) {
return assign(s, traitsLength(s));
}
basic_fbstring& assign(std::initializer_list<value_type> il) {
return assign(il.begin(), il.end());
}
template <class ItOrLength, class ItOrChar>
basic_fbstring& assign(ItOrLength first_or_n, ItOrChar last_or_c) {
return replace(begin(), end(), first_or_n, last_or_c);
}
basic_fbstring& insert(size_type pos1, const basic_fbstring& str) {
return insert(pos1, str.data(), str.size());
}
basic_fbstring& insert(
size_type pos1,
const basic_fbstring& str,
size_type pos2,
size_type n) {
enforce<std::out_of_range>(pos2 <= str.length(), "");
procrustes(n, str.length() - pos2);
return insert(pos1, str.data() + pos2, n);
}
basic_fbstring& insert(size_type pos, const value_type* s, size_type n) {
enforce<std::out_of_range>(pos <= length(), "");
insert(begin() + pos, s, s + n);
return *this;
}
basic_fbstring& insert(size_type pos, const value_type* s) {
return insert(pos, s, traitsLength(s));
}
basic_fbstring& insert(size_type pos, size_type n, value_type c) {
enforce<std::out_of_range>(pos <= length(), "");
insert(begin() + pos, n, c);
return *this;
}
iterator insert(const_iterator p, const value_type c) {
const size_type pos = p - cbegin();
insert(p, 1, c);
return begin() + pos;
}
private:
typedef std::basic_istream<value_type, traits_type> istream_type;
istream_type& getlineImpl(istream_type& is, value_type delim);
public:
friend inline istream_type&
getline(istream_type& is, basic_fbstring& str, value_type delim) {
return str.getlineImpl(is, delim);
}
friend inline istream_type& getline(istream_type& is, basic_fbstring& str) {
return getline(is, str, '\n');
}
private:
iterator
insertImplDiscr(const_iterator i, size_type n, value_type c, std::true_type);
template <class InputIter>
iterator
insertImplDiscr(const_iterator i, InputIter b, InputIter e, std::false_type);
template <class FwdIterator>
iterator insertImpl(
const_iterator i,
FwdIterator s1,
FwdIterator s2,
std::forward_iterator_tag);
template <class InputIterator>
iterator insertImpl(
const_iterator i,
InputIterator b,
InputIterator e,
std::input_iterator_tag);
public:
template <class ItOrLength, class ItOrChar>
iterator insert(const_iterator p, ItOrLength first_or_n, ItOrChar last_or_c) {
using Sel = bool_constant<std::numeric_limits<ItOrLength>::is_specialized>;
return insertImplDiscr(p, first_or_n, last_or_c, Sel());
}
iterator insert(const_iterator p, std::initializer_list<value_type> il) {
return insert(p, il.begin(), il.end());
}
basic_fbstring& erase(size_type pos = 0, size_type n = npos) {
Invariant checker(*this);
enforce<std::out_of_range>(pos <= length(), "");
procrustes(n, length() - pos);
std::copy(begin() + pos + n, end(), begin() + pos);
resize(length() - n);
return *this;
}
iterator erase(iterator position) {
const size_type pos(position - begin());
enforce<std::out_of_range>(pos <= size(), "");
erase(pos, 1);
return begin() + pos;
}
iterator erase(iterator first, iterator last) {
const size_type pos(first - begin());
erase(pos, last - first);
return begin() + pos;
}
// Replaces at most n1 chars of *this, starting with pos1 with the
// content of str
basic_fbstring&
replace(size_type pos1, size_type n1, const basic_fbstring& str) {
return replace(pos1, n1, str.data(), str.size());
}
// Replaces at most n1 chars of *this, starting with pos1,
// with at most n2 chars of str starting with pos2
basic_fbstring& replace(
size_type pos1,
size_type n1,
const basic_fbstring& str,
size_type pos2,
size_type n2) {
enforce<std::out_of_range>(pos2 <= str.length(), "");
return replace(
pos1, n1, str.data() + pos2, std::min(n2, str.size() - pos2));
}
// Replaces at most n1 chars of *this, starting with pos, with chars from s
basic_fbstring& replace(size_type pos, size_type n1, const value_type* s) {
return replace(pos, n1, s, traitsLength(s));
}
// Replaces at most n1 chars of *this, starting with pos, with n2
// occurrences of c
//
// consolidated with
//
// Replaces at most n1 chars of *this, starting with pos, with at
// most n2 chars of str. str must have at least n2 chars.
template <class StrOrLength, class NumOrChar>
basic_fbstring&
replace(size_type pos, size_type n1, StrOrLength s_or_n2, NumOrChar n_or_c) {
Invariant checker(*this);
enforce<std::out_of_range>(pos <= size(), "");
procrustes(n1, length() - pos);
const iterator b = begin() + pos;
return replace(b, b + n1, s_or_n2, n_or_c);
}
basic_fbstring& replace(iterator i1, iterator i2, const basic_fbstring& str) {
return replace(i1, i2, str.data(), str.length());
}
basic_fbstring& replace(iterator i1, iterator i2, const value_type* s) {
return replace(i1, i2, s, traitsLength(s));
}
private:
basic_fbstring& replaceImplDiscr(
iterator i1,
iterator i2,
const value_type* s,
size_type n,
std::integral_constant<int, 2>);
basic_fbstring& replaceImplDiscr(
iterator i1,
iterator i2,
size_type n2,
value_type c,
std::integral_constant<int, 1>);
template <class InputIter>
basic_fbstring& replaceImplDiscr(
iterator i1,
iterator i2,
InputIter b,
InputIter e,
std::integral_constant<int, 0>);
private:
template <class FwdIterator>
bool replaceAliased(
iterator /* i1 */,
iterator /* i2 */,
FwdIterator /* s1 */,
FwdIterator /* s2 */,
std::false_type) {
return false;
}
template <class FwdIterator>
bool replaceAliased(
iterator i1,
iterator i2,
FwdIterator s1,
FwdIterator s2,
std::true_type);
template <class FwdIterator>
void replaceImpl(
iterator i1,
iterator i2,
FwdIterator s1,
FwdIterator s2,
std::forward_iterator_tag);
template <class InputIterator>
void replaceImpl(
iterator i1,
iterator i2,
InputIterator b,
InputIterator e,
std::input_iterator_tag);
public:
template <class T1, class T2>
basic_fbstring&
replace(iterator i1, iterator i2, T1 first_or_n_or_s, T2 last_or_c_or_n) {
constexpr bool num1 = std::numeric_limits<T1>::is_specialized,
num2 = std::numeric_limits<T2>::is_specialized;
using Sel =
std::integral_constant<int, num1 ? (num2 ? 1 : -1) : (num2 ? 2 : 0)>;
return replaceImplDiscr(i1, i2, first_or_n_or_s, last_or_c_or_n, Sel());
}
size_type copy(value_type* s, size_type n, size_type pos = 0) const {
enforce<std::out_of_range>(pos <= size(), "");
procrustes(n, size() - pos);
if (n != 0) {
fbstring_detail::podCopy(data() + pos, data() + pos + n, s);
}
return n;
}
void swap(basic_fbstring& rhs) {
store_.swap(rhs.store_);
}
const value_type* c_str() const {
return store_.c_str();
}
const value_type* data() const {
return c_str();
}
value_type* data() {
return store_.data();
}
allocator_type get_allocator() const {
return allocator_type();
}
size_type find(const basic_fbstring& str, size_type pos = 0) const {
return find(str.data(), pos, str.length());
}
size_type find(const value_type* needle, size_type pos, size_type nsize)
const;
size_type find(const value_type* s, size_type pos = 0) const {
return find(s, pos, traitsLength(s));
}
size_type find(value_type c, size_type pos = 0) const {
return find(&c, pos, 1);
}
size_type rfind(const basic_fbstring& str, size_type pos = npos) const {
return rfind(str.data(), pos, str.length());
}
size_type rfind(const value_type* s, size_type pos, size_type n) const;
size_type rfind(const value_type* s, size_type pos = npos) const {
return rfind(s, pos, traitsLength(s));
}
size_type rfind(value_type c, size_type pos = npos) const {
return rfind(&c, pos, 1);
}
size_type find_first_of(const basic_fbstring& str, size_type pos = 0) const {
return find_first_of(str.data(), pos, str.length());
}
size_type find_first_of(const value_type* s, size_type pos, size_type n)
const;
size_type find_first_of(const value_type* s, size_type pos = 0) const {
return find_first_of(s, pos, traitsLength(s));
}
size_type find_first_of(value_type c, size_type pos = 0) const {
return find_first_of(&c, pos, 1);
}
size_type find_last_of(const basic_fbstring& str, size_type pos = npos)
const {
return find_last_of(str.data(), pos, str.length());
}
size_type find_last_of(const value_type* s, size_type pos, size_type n) const;
size_type find_last_of(const value_type* s, size_type pos = npos) const {
return find_last_of(s, pos, traitsLength(s));
}
size_type find_last_of(value_type c, size_type pos = npos) const {
return find_last_of(&c, pos, 1);
}
size_type find_first_not_of(const basic_fbstring& str, size_type pos = 0)
const {
return find_first_not_of(str.data(), pos, str.size());
}
size_type find_first_not_of(const value_type* s, size_type pos, size_type n)
const;
size_type find_first_not_of(const value_type* s, size_type pos = 0) const {
return find_first_not_of(s, pos, traitsLength(s));
}
size_type find_first_not_of(value_type c, size_type pos = 0) const {
return find_first_not_of(&c, pos, 1);
}
size_type find_last_not_of(const basic_fbstring& str, size_type pos = npos)
const {
return find_last_not_of(str.data(), pos, str.length());
}
size_type find_last_not_of(const value_type* s, size_type pos, size_type n)
const;
size_type find_last_not_of(const value_type* s, size_type pos = npos) const {
return find_last_not_of(s, pos, traitsLength(s));
}
size_type find_last_not_of(value_type c, size_type pos = npos) const {
return find_last_not_of(&c, pos, 1);
}
basic_fbstring substr(size_type pos = 0, size_type n = npos) const& {
enforce<std::out_of_range>(pos <= size(), "");
return basic_fbstring(data() + pos, std::min(n, size() - pos));
}
basic_fbstring substr(size_type pos = 0, size_type n = npos) && {
enforce<std::out_of_range>(pos <= size(), "");
erase(0, pos);
if (n < size()) {
resize(n);
}
return std::move(*this);
}
int compare(const basic_fbstring& str) const {
// FIX due to Goncalo N M de Carvalho July 18, 2005
return compare(0, size(), str);
}
int compare(size_type pos1, size_type n1, const basic_fbstring& str) const {
return compare(pos1, n1, str.data(), str.size());
}
int compare(size_type pos1, size_type n1, const value_type* s) const {
return compare(pos1, n1, s, traitsLength(s));
}
int compare(size_type pos1, size_type n1, const value_type* s, size_type n2)
const {
enforce<std::out_of_range>(pos1 <= size(), "");
procrustes(n1, size() - pos1);
// The line below fixed by Jean-Francois Bastien, 04-23-2007. Thanks!
const int r = traits_type::compare(pos1 + data(), s, std::min(n1, n2));
return r != 0 ? r : n1 > n2 ? 1 : n1 < n2 ? -1 : 0;
}
int compare(
size_type pos1,
size_type n1,
const basic_fbstring& str,
size_type pos2,
size_type n2) const {
enforce<std::out_of_range>(pos2 <= str.size(), "");
return compare(
pos1, n1, str.data() + pos2, std::min(n2, str.size() - pos2));
}
// Code from Jean-Francois Bastien (03/26/2007)
int compare(const value_type* s) const {
// Could forward to compare(0, size(), s, traitsLength(s))
// but that does two extra checks
const size_type n1(size()), n2(traitsLength(s));
const int r = traits_type::compare(data(), s, std::min(n1, n2));
return r != 0 ? r : n1 > n2 ? 1 : n1 < n2 ? -1 : 0;
}
private:
// Data
Storage store_;
};
template <typename E, class T, class A, class S>
FOLLY_NOINLINE inline typename basic_fbstring<E, T, A, S>::size_type
basic_fbstring<E, T, A, S>::traitsLength(const value_type* s) {
return s ? traits_type::length(s)
: (throw_exception<std::logic_error>(
"basic_fbstring: null pointer initializer not valid"),
0);
}
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::operator=(
const basic_fbstring& lhs) {
Invariant checker(*this);
if (FOLLY_UNLIKELY(&lhs == this)) {
return *this;
}
return assign(lhs.data(), lhs.size());
}
// Move assignment
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::operator=(
basic_fbstring&& goner) noexcept {
if (FOLLY_UNLIKELY(&goner == this)) {
// Compatibility with std::basic_string<>,
// C++11 21.4.2 [string.cons] / 23 requires self-move-assignment support.
return *this;
}
// No need of this anymore
this->~basic_fbstring();
// Move the goner into this
new (&store_) S(std::move(goner.store_));
return *this;
}
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::operator=(
value_type c) {
Invariant checker(*this);
if (empty()) {
store_.expandNoinit(1);
} else if (store_.isShared()) {
basic_fbstring(1, c).swap(*this);
return *this;
} else {
store_.shrink(size() - 1);
}
front() = c;
return *this;
}
template <typename E, class T, class A, class S>
inline void basic_fbstring<E, T, A, S>::resize(
const size_type n,
const value_type c /*= value_type()*/) {
Invariant checker(*this);
auto size = this->size();
if (n <= size) {
store_.shrink(size - n);
} else {
auto const delta = n - size;
auto pData = store_.expandNoinit(delta);
fbstring_detail::podFill(pData, pData + delta, c);
}
assert(this->size() == n);
}
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::append(
const basic_fbstring& str) {
#ifndef NDEBUG
auto desiredSize = size() + str.size();
#endif
append(str.data(), str.size());
assert(size() == desiredSize);
return *this;
}
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::append(
const basic_fbstring& str,
const size_type pos,
size_type n) {
const size_type sz = str.size();
enforce<std::out_of_range>(pos <= sz, "");
procrustes(n, sz - pos);
return append(str.data() + pos, n);
}
template <typename E, class T, class A, class S>
FOLLY_NOINLINE inline basic_fbstring<E, T, A, S>&
basic_fbstring<E, T, A, S>::append(const value_type* s, size_type n) {
Invariant checker(*this);
if (FOLLY_UNLIKELY(!n)) {
// Unlikely but must be done
return *this;
}
auto const oldSize = size();
auto const oldData = data();
auto pData = store_.expandNoinit(n, /* expGrowth = */ true);
// Check for aliasing (rare). We could use "<=" here but in theory
// those do not work for pointers unless the pointers point to
// elements in the same array. For that reason we use
// std::less_equal, which is guaranteed to offer a total order
// over pointers. See discussion at http://goo.gl/Cy2ya for more
// info.
std::less_equal<const value_type*> le;
if (FOLLY_UNLIKELY(le(oldData, s) && !le(oldData + oldSize, s))) {
assert(le(s + n, oldData + oldSize));
// expandNoinit() could have moved the storage, restore the source.
s = data() + (s - oldData);
fbstring_detail::podMove(s, s + n, pData);
} else {
fbstring_detail::podCopy(s, s + n, pData);
}
assert(size() == oldSize + n);
return *this;
}
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::append(
size_type n,
value_type c) {
Invariant checker(*this);
auto pData = store_.expandNoinit(n, /* expGrowth = */ true);
fbstring_detail::podFill(pData, pData + n, c);
return *this;
}
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::assign(
const basic_fbstring& str,
const size_type pos,
size_type n) {
const size_type sz = str.size();
enforce<std::out_of_range>(pos <= sz, "");
procrustes(n, sz - pos);
return assign(str.data() + pos, n);
}
template <typename E, class T, class A, class S>
FOLLY_NOINLINE inline basic_fbstring<E, T, A, S>&
basic_fbstring<E, T, A, S>::assign(const value_type* s, const size_type n) {
Invariant checker(*this);
if (n == 0) {
resize(0);
} else if (size() >= n) {
// s can alias this, we need to use podMove.
fbstring_detail::podMove(s, s + n, store_.mutableData());
store_.shrink(size() - n);
assert(size() == n);
} else {
// If n is larger than size(), s cannot alias this string's
// storage.
resize(0);
// Do not use exponential growth here: assign() should be tight,
// to mirror the behavior of the equivalent constructor.
fbstring_detail::podCopy(s, s + n, store_.expandNoinit(n));
}
assert(size() == n);
return *this;
}
template <typename E, class T, class A, class S>
inline typename basic_fbstring<E, T, A, S>::istream_type&
basic_fbstring<E, T, A, S>::getlineImpl(istream_type& is, value_type delim) {
Invariant checker(*this);
clear();
size_t size = 0;
while (true) {
size_t avail = capacity() - size;
// fbstring has 1 byte extra capacity for the null terminator,
// and getline null-terminates the read string.
is.getline(store_.expandNoinit(avail), avail + 1, delim);
size += is.gcount();
if (is.bad() || is.eof() || !is.fail()) {
// Done by either failure, end of file, or normal read.
if (!is.bad() && !is.eof()) {
--size; // gcount() also accounts for the delimiter.
}
resize(size);
break;
}
assert(size == this->size());
assert(size == capacity());
// Start at minimum allocation 63 + terminator = 64.
reserve(std::max<size_t>(63, 3 * size / 2));
// Clear the error so we can continue reading.
is.clear();
}
return is;
}
template <typename E, class T, class A, class S>
inline typename basic_fbstring<E, T, A, S>::size_type
basic_fbstring<E, T, A, S>::find(
const value_type* needle,
const size_type pos,
const size_type nsize) const {
auto const size = this->size();
// nsize + pos can overflow (eg pos == npos), guard against that by checking
// that nsize + pos does not wrap around.
if (nsize + pos > size || nsize + pos < pos) {
return npos;
}
if (nsize == 0) {
return pos;
}
// Don't use std::search, use a Boyer-Moore-like trick by comparing
// the last characters first
auto const haystack = data();
auto const nsize_1 = nsize - 1;
auto const lastNeedle = needle[nsize_1];
// Boyer-Moore skip value for the last char in the needle. Zero is
// not a valid value; skip will be computed the first time it's
// needed.
size_type skip = 0;
const E* i = haystack + pos;
auto iEnd = haystack + size - nsize_1;
while (i < iEnd) {
// Boyer-Moore: match the last element in the needle
while (i[nsize_1] != lastNeedle) {
if (++i == iEnd) {
// not found
return npos;
}
}
// Here we know that the last char matches
// Continue in pedestrian mode
for (size_t j = 0;;) {
assert(j < nsize);
if (i[j] != needle[j]) {
// Not found, we can skip
// Compute the skip value lazily
if (skip == 0) {
skip = 1;
while (skip <= nsize_1 && needle[nsize_1 - skip] != lastNeedle) {
++skip;
}
}
i += skip;
break;
}
// Check if done searching
if (++j == nsize) {
// Yay
return i - haystack;
}
}
}
return npos;
}
template <typename E, class T, class A, class S>
inline typename basic_fbstring<E, T, A, S>::iterator
basic_fbstring<E, T, A, S>::insertImplDiscr(
const_iterator i,
size_type n,
value_type c,
std::true_type) {
Invariant checker(*this);
assert(i >= cbegin() && i <= cend());
const size_type pos = i - cbegin();
auto oldSize = size();
store_.expandNoinit(n, /* expGrowth = */ true);
auto b = begin();
fbstring_detail::podMove(b + pos, b + oldSize, b + pos + n);
fbstring_detail::podFill(b + pos, b + pos + n, c);
return b + pos;
}
template <typename E, class T, class A, class S>
template <class InputIter>
inline typename basic_fbstring<E, T, A, S>::iterator
basic_fbstring<E, T, A, S>::insertImplDiscr(
const_iterator i,
InputIter b,
InputIter e,
std::false_type) {
return insertImpl(
i, b, e, typename std::iterator_traits<InputIter>::iterator_category());
}
template <typename E, class T, class A, class S>
template <class FwdIterator>
inline typename basic_fbstring<E, T, A, S>::iterator
basic_fbstring<E, T, A, S>::insertImpl(
const_iterator i,
FwdIterator s1,
FwdIterator s2,
std::forward_iterator_tag) {
Invariant checker(*this);
assert(i >= cbegin() && i <= cend());
const size_type pos = i - cbegin();
auto n = std::distance(s1, s2);
assert(n >= 0);
auto oldSize = size();
store_.expandNoinit(n, /* expGrowth = */ true);
auto b = begin();
fbstring_detail::podMove(b + pos, b + oldSize, b + pos + n);
std::copy(s1, s2, b + pos);
return b + pos;
}
template <typename E, class T, class A, class S>
template <class InputIterator>
inline typename basic_fbstring<E, T, A, S>::iterator
basic_fbstring<E, T, A, S>::insertImpl(
const_iterator i,
InputIterator b,
InputIterator e,
std::input_iterator_tag) {
const auto pos = i - cbegin();
basic_fbstring temp(cbegin(), i);
for (; b != e; ++b) {
temp.push_back(*b);
}
temp.append(i, cend());
swap(temp);
return begin() + pos;
}
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::replaceImplDiscr(
iterator i1,
iterator i2,
const value_type* s,
size_type n,
std::integral_constant<int, 2>) {
assert(i1 <= i2);
assert(begin() <= i1 && i1 <= end());
assert(begin() <= i2 && i2 <= end());
return replace(i1, i2, s, s + n);
}
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::replaceImplDiscr(
iterator i1,
iterator i2,
size_type n2,
value_type c,
std::integral_constant<int, 1>) {
const size_type n1 = i2 - i1;
if (n1 > n2) {
std::fill(i1, i1 + n2, c);
erase(i1 + n2, i2);
} else {
std::fill(i1, i2, c);
insert(i2, n2 - n1, c);
}
assert(isSane());
return *this;
}
template <typename E, class T, class A, class S>
template <class InputIter>
inline basic_fbstring<E, T, A, S>& basic_fbstring<E, T, A, S>::replaceImplDiscr(
iterator i1,
iterator i2,
InputIter b,
InputIter e,
std::integral_constant<int, 0>) {
using Cat = typename std::iterator_traits<InputIter>::iterator_category;
replaceImpl(i1, i2, b, e, Cat());
return *this;
}
template <typename E, class T, class A, class S>
template <class FwdIterator>
inline bool basic_fbstring<E, T, A, S>::replaceAliased(
iterator i1,
iterator i2,
FwdIterator s1,
FwdIterator s2,
std::true_type) {
std::less_equal<const value_type*> le{};
const bool aliased = le(&*begin(), &*s1) && le(&*s1, &*end());
if (!aliased) {
return false;
}
// Aliased replace, copy to new string
basic_fbstring temp;
temp.reserve(size() - (i2 - i1) + std::distance(s1, s2));
temp.append(begin(), i1).append(s1, s2).append(i2, end());
swap(temp);
return true;
}
template <typename E, class T, class A, class S>
template <class FwdIterator>
inline void basic_fbstring<E, T, A, S>::replaceImpl(
iterator i1,
iterator i2,
FwdIterator s1,
FwdIterator s2,
std::forward_iterator_tag) {
Invariant checker(*this);
// Handle aliased replace
using Sel = bool_constant<
std::is_same<FwdIterator, iterator>::value ||
std::is_same<FwdIterator, const_iterator>::value>;
if (replaceAliased(i1, i2, s1, s2, Sel())) {
return;
}
auto const n1 = i2 - i1;
assert(n1 >= 0);
auto const n2 = std::distance(s1, s2);
assert(n2 >= 0);
if (n1 > n2) {
// shrinks
std::copy(s1, s2, i1);
erase(i1 + n2, i2);
} else {
// grows
s1 = fbstring_detail::copy_n(s1, n1, i1).first;
insert(i2, s1, s2);
}
assert(isSane());
}
template <typename E, class T, class A, class S>
template <class InputIterator>
inline void basic_fbstring<E, T, A, S>::replaceImpl(
iterator i1,
iterator i2,
InputIterator b,
InputIterator e,
std::input_iterator_tag) {
basic_fbstring temp(begin(), i1);
temp.append(b, e).append(i2, end());
swap(temp);
}
template <typename E, class T, class A, class S>
inline typename basic_fbstring<E, T, A, S>::size_type
basic_fbstring<E, T, A, S>::rfind(
const value_type* s,
size_type pos,
size_type n) const {
if (n > length()) {
return npos;
}
pos = std::min(pos, length() - n);
if (n == 0) {
return pos;
}
const_iterator i(begin() + pos);
for (;; --i) {
if (traits_type::eq(*i, *s) && traits_type::compare(&*i, s, n) == 0) {
return i - begin();
}
if (i == begin()) {
break;
}
}
return npos;
}
template <typename E, class T, class A, class S>
inline typename basic_fbstring<E, T, A, S>::size_type
basic_fbstring<E, T, A, S>::find_first_of(
const value_type* s,
size_type pos,
size_type n) const {
if (pos > length() || n == 0) {
return npos;
}
const_iterator i(begin() + pos), finish(end());
for (; i != finish; ++i) {
if (traits_type::find(s, n, *i) != nullptr) {
return i - begin();
}
}
return npos;
}
template <typename E, class T, class A, class S>
inline typename basic_fbstring<E, T, A, S>::size_type
basic_fbstring<E, T, A, S>::find_last_of(
const value_type* s,
size_type pos,
size_type n) const {
if (!empty() && n > 0) {
pos = std::min(pos, length() - 1);
const_iterator i(begin() + pos);
for (;; --i) {
if (traits_type::find(s, n, *i) != nullptr) {
return i - begin();
}
if (i == begin()) {
break;
}
}
}
return npos;
}
template <typename E, class T, class A, class S>
inline typename basic_fbstring<E, T, A, S>::size_type
basic_fbstring<E, T, A, S>::find_first_not_of(
const value_type* s,
size_type pos,
size_type n) const {
if (pos < length()) {
const_iterator i(begin() + pos), finish(end());
for (; i != finish; ++i) {
if (traits_type::find(s, n, *i) == nullptr) {
return i - begin();
}
}
}
return npos;
}
template <typename E, class T, class A, class S>
inline typename basic_fbstring<E, T, A, S>::size_type
basic_fbstring<E, T, A, S>::find_last_not_of(
const value_type* s,
size_type pos,
size_type n) const {
if (!this->empty()) {
pos = std::min(pos, size() - 1);
const_iterator i(begin() + pos);
for (;; --i) {
if (traits_type::find(s, n, *i) == nullptr) {
return i - begin();
}
if (i == begin()) {
break;
}
}
}
return npos;
}
// non-member functions
// C++11 21.4.8.1/1
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
const basic_fbstring<E, T, A, S>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
basic_fbstring<E, T, A, S> result;
result.reserve(lhs.size() + rhs.size());
result.append(lhs).append(rhs);
return result;
}
// C++11 21.4.8.1/2
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
basic_fbstring<E, T, A, S>&& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return std::move(lhs.append(rhs));
}
// C++11 21.4.8.1/3
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
const basic_fbstring<E, T, A, S>& lhs,
basic_fbstring<E, T, A, S>&& rhs) {
if (rhs.capacity() >= lhs.size() + rhs.size()) {
// Good, at least we don't need to reallocate
return std::move(rhs.insert(0, lhs));
}
// Meh, no go. Forward to operator+(const&, const&).
auto const& rhsC = rhs;
return lhs + rhsC;
}
// C++11 21.4.8.1/4
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
basic_fbstring<E, T, A, S>&& lhs,
basic_fbstring<E, T, A, S>&& rhs) {
return std::move(lhs.append(rhs));
}
// C++11 21.4.8.1/5
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
const E* lhs,
const basic_fbstring<E, T, A, S>& rhs) {
//
basic_fbstring<E, T, A, S> result;
const auto len = basic_fbstring<E, T, A, S>::traits_type::length(lhs);
result.reserve(len + rhs.size());
result.append(lhs, len).append(rhs);
return result;
}
// C++11 21.4.8.1/6
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
const E* lhs,
basic_fbstring<E, T, A, S>&& rhs) {
//
const auto len = basic_fbstring<E, T, A, S>::traits_type::length(lhs);
if (rhs.capacity() >= len + rhs.size()) {
// Good, at least we don't need to reallocate
rhs.insert(rhs.begin(), lhs, lhs + len);
return std::move(rhs);
}
// Meh, no go. Do it by hand since we have len already.
basic_fbstring<E, T, A, S> result;
result.reserve(len + rhs.size());
result.append(lhs, len).append(rhs);
return result;
}
// C++11 21.4.8.1/7
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
E lhs,
const basic_fbstring<E, T, A, S>& rhs) {
basic_fbstring<E, T, A, S> result;
result.reserve(1 + rhs.size());
result.push_back(lhs);
result.append(rhs);
return result;
}
// C++11 21.4.8.1/8
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
E lhs,
basic_fbstring<E, T, A, S>&& rhs) {
//
if (rhs.capacity() > rhs.size()) {
// Good, at least we don't need to reallocate
rhs.insert(rhs.begin(), lhs);
return std::move(rhs);
}
// Meh, no go. Forward to operator+(E, const&).
auto const& rhsC = rhs;
return lhs + rhsC;
}
// C++11 21.4.8.1/9
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
const basic_fbstring<E, T, A, S>& lhs,
const E* rhs) {
typedef typename basic_fbstring<E, T, A, S>::size_type size_type;
typedef typename basic_fbstring<E, T, A, S>::traits_type traits_type;
basic_fbstring<E, T, A, S> result;
const size_type len = traits_type::length(rhs);
result.reserve(lhs.size() + len);
result.append(lhs).append(rhs, len);
return result;
}
// C++11 21.4.8.1/10
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
basic_fbstring<E, T, A, S>&& lhs,
const E* rhs) {
//
return std::move(lhs += rhs);
}
// C++11 21.4.8.1/11
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
const basic_fbstring<E, T, A, S>& lhs,
E rhs) {
basic_fbstring<E, T, A, S> result;
result.reserve(lhs.size() + 1);
result.append(lhs);
result.push_back(rhs);
return result;
}
// C++11 21.4.8.1/12
template <typename E, class T, class A, class S>
inline basic_fbstring<E, T, A, S> operator+(
basic_fbstring<E, T, A, S>&& lhs,
E rhs) {
//
return std::move(lhs += rhs);
}
template <typename E, class T, class A, class S>
inline bool operator==(
const basic_fbstring<E, T, A, S>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
}
template <typename E, class T, class A, class S>
inline bool operator==(
const typename basic_fbstring<E, T, A, S>::value_type* lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return rhs == lhs;
}
template <typename E, class T, class A, class S>
inline bool operator==(
const basic_fbstring<E, T, A, S>& lhs,
const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
return lhs.compare(rhs) == 0;
}
template <typename E, class T, class A, class S>
inline bool operator!=(
const basic_fbstring<E, T, A, S>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return !(lhs == rhs);
}
template <typename E, class T, class A, class S>
inline bool operator!=(
const typename basic_fbstring<E, T, A, S>::value_type* lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return !(lhs == rhs);
}
template <typename E, class T, class A, class S>
inline bool operator!=(
const basic_fbstring<E, T, A, S>& lhs,
const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
return !(lhs == rhs);
}
template <typename E, class T, class A, class S>
inline bool operator<(
const basic_fbstring<E, T, A, S>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return lhs.compare(rhs) < 0;
}
template <typename E, class T, class A, class S>
inline bool operator<(
const basic_fbstring<E, T, A, S>& lhs,
const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
return lhs.compare(rhs) < 0;
}
template <typename E, class T, class A, class S>
inline bool operator<(
const typename basic_fbstring<E, T, A, S>::value_type* lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return rhs.compare(lhs) > 0;
}
template <typename E, class T, class A, class S>
inline bool operator>(
const basic_fbstring<E, T, A, S>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return rhs < lhs;
}
template <typename E, class T, class A, class S>
inline bool operator>(
const basic_fbstring<E, T, A, S>& lhs,
const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
return rhs < lhs;
}
template <typename E, class T, class A, class S>
inline bool operator>(
const typename basic_fbstring<E, T, A, S>::value_type* lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return rhs < lhs;
}
template <typename E, class T, class A, class S>
inline bool operator<=(
const basic_fbstring<E, T, A, S>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return !(rhs < lhs);
}
template <typename E, class T, class A, class S>
inline bool operator<=(
const basic_fbstring<E, T, A, S>& lhs,
const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
return !(rhs < lhs);
}
template <typename E, class T, class A, class S>
inline bool operator<=(
const typename basic_fbstring<E, T, A, S>::value_type* lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return !(rhs < lhs);
}
template <typename E, class T, class A, class S>
inline bool operator>=(
const basic_fbstring<E, T, A, S>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return !(lhs < rhs);
}
template <typename E, class T, class A, class S>
inline bool operator>=(
const basic_fbstring<E, T, A, S>& lhs,
const typename basic_fbstring<E, T, A, S>::value_type* rhs) {
return !(lhs < rhs);
}
template <typename E, class T, class A, class S>
inline bool operator>=(
const typename basic_fbstring<E, T, A, S>::value_type* lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return !(lhs < rhs);
}
// C++11 21.4.8.8
template <typename E, class T, class A, class S>
void swap(basic_fbstring<E, T, A, S>& lhs, basic_fbstring<E, T, A, S>& rhs) {
lhs.swap(rhs);
}
// TODO: make this faster.
template <typename E, class T, class A, class S>
inline std::basic_istream<
typename basic_fbstring<E, T, A, S>::value_type,
typename basic_fbstring<E, T, A, S>::traits_type>&
operator>>(
std::basic_istream<
typename basic_fbstring<E, T, A, S>::value_type,
typename basic_fbstring<E, T, A, S>::traits_type>& is,
basic_fbstring<E, T, A, S>& str) {
typedef std::basic_istream<
typename basic_fbstring<E, T, A, S>::value_type,
typename basic_fbstring<E, T, A, S>::traits_type>
_istream_type;
typename _istream_type::sentry sentry(is);
size_t extracted = 0;
typename _istream_type::iostate err = _istream_type::goodbit;
if (sentry) {
auto n = is.width();
if (n <= 0) {
n = str.max_size();
}
str.erase();
for (auto got = is.rdbuf()->sgetc(); extracted != size_t(n); ++extracted) {
if (got == T::eof()) {
err |= _istream_type::eofbit;
is.width(0);
break;
}
if (isspace(got)) {
break;
}
str.push_back(got);
got = is.rdbuf()->snextc();
}
}
if (!extracted) {
err |= _istream_type::failbit;
}
if (err) {
is.setstate(err);
}
return is;
}
template <typename E, class T, class A, class S>
inline std::basic_ostream<
typename basic_fbstring<E, T, A, S>::value_type,
typename basic_fbstring<E, T, A, S>::traits_type>&
operator<<(
std::basic_ostream<
typename basic_fbstring<E, T, A, S>::value_type,
typename basic_fbstring<E, T, A, S>::traits_type>& os,
const basic_fbstring<E, T, A, S>& str) {
#if _LIBCPP_VERSION
typedef std::basic_ostream<
typename basic_fbstring<E, T, A, S>::value_type,
typename basic_fbstring<E, T, A, S>::traits_type>
_ostream_type;
typename _ostream_type::sentry _s(os);
if (_s) {
typedef std::ostreambuf_iterator<
typename basic_fbstring<E, T, A, S>::value_type,
typename basic_fbstring<E, T, A, S>::traits_type>
_Ip;
size_t __len = str.size();
bool __left =
(os.flags() & _ostream_type::adjustfield) == _ostream_type::left;
if (__pad_and_output(
_Ip(os),
str.data(),
__left ? str.data() + __len : str.data(),
str.data() + __len,
os,
os.fill())
.failed()) {
os.setstate(_ostream_type::badbit | _ostream_type::failbit);
}
}
#elif defined(_MSC_VER)
typedef decltype(os.precision()) streamsize;
// MSVC doesn't define __ostream_insert
os.write(str.data(), static_cast<streamsize>(str.size()));
#else
std::__ostream_insert(os, str.data(), str.size());
#endif
return os;
}
template <typename E1, class T, class A, class S>
constexpr typename basic_fbstring<E1, T, A, S>::size_type
basic_fbstring<E1, T, A, S>::npos;
// basic_string compatibility routines
template <typename E, class T, class A, class S, class A2>
inline bool operator==(
const basic_fbstring<E, T, A, S>& lhs,
const std::basic_string<E, T, A2>& rhs) {
return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()) == 0;
}
template <typename E, class T, class A, class S, class A2>
inline bool operator==(
const std::basic_string<E, T, A2>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return rhs == lhs;
}
template <typename E, class T, class A, class S, class A2>
inline bool operator!=(
const basic_fbstring<E, T, A, S>& lhs,
const std::basic_string<E, T, A2>& rhs) {
return !(lhs == rhs);
}
template <typename E, class T, class A, class S, class A2>
inline bool operator!=(
const std::basic_string<E, T, A2>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return !(lhs == rhs);
}
template <typename E, class T, class A, class S, class A2>
inline bool operator<(
const basic_fbstring<E, T, A, S>& lhs,
const std::basic_string<E, T, A2>& rhs) {
return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()) < 0;
}
template <typename E, class T, class A, class S, class A2>
inline bool operator>(
const basic_fbstring<E, T, A, S>& lhs,
const std::basic_string<E, T, A2>& rhs) {
return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()) > 0;
}
template <typename E, class T, class A, class S, class A2>
inline bool operator<(
const std::basic_string<E, T, A2>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return rhs > lhs;
}
template <typename E, class T, class A, class S, class A2>
inline bool operator>(
const std::basic_string<E, T, A2>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return rhs < lhs;
}
template <typename E, class T, class A, class S, class A2>
inline bool operator<=(
const basic_fbstring<E, T, A, S>& lhs,
const std::basic_string<E, T, A2>& rhs) {
return !(lhs > rhs);
}
template <typename E, class T, class A, class S, class A2>
inline bool operator>=(
const basic_fbstring<E, T, A, S>& lhs,
const std::basic_string<E, T, A2>& rhs) {
return !(lhs < rhs);
}
template <typename E, class T, class A, class S, class A2>
inline bool operator<=(
const std::basic_string<E, T, A2>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return !(lhs > rhs);
}
template <typename E, class T, class A, class S, class A2>
inline bool operator>=(
const std::basic_string<E, T, A2>& lhs,
const basic_fbstring<E, T, A, S>& rhs) {
return !(lhs < rhs);
}
typedef basic_fbstring<char> fbstring;
// fbstring is relocatable
template <class T, class R, class A, class S>
FOLLY_ASSUME_RELOCATABLE(basic_fbstring<T, R, A, S>);
// Compatibility function, to make sure toStdString(s) can be called
// to convert a std::string or fbstring variable s into type std::string
// with very little overhead if s was already std::string
inline std::string toStdString(const folly::fbstring& s) {
return std::string(s.data(), s.size());
}
inline const std::string& toStdString(const std::string& s) {
return s;
}
// If called with a temporary, the compiler will select this overload instead
// of the above, so we don't return a (lvalue) reference to a temporary.
inline std::string&& toStdString(std::string&& s) {
return std::move(s);
}
} // namespace folly
// Hash functions to make fbstring usable with e.g. unordered_map
#define FOLLY_FBSTRING_HASH1(T) \
template <> \
struct hash<::folly::basic_fbstring<T>> { \
size_t operator()(const ::folly::basic_fbstring<T>& s) const { \
return ::folly::hash::fnv32_buf(s.data(), s.size() * sizeof(T)); \
} \
};
// The C++11 standard says that these four are defined for basic_string
#define FOLLY_FBSTRING_HASH \
FOLLY_FBSTRING_HASH1(char) \
FOLLY_FBSTRING_HASH1(char16_t) \
FOLLY_FBSTRING_HASH1(char32_t) \
FOLLY_FBSTRING_HASH1(wchar_t)
namespace std {
FOLLY_FBSTRING_HASH
} // namespace std
#undef FOLLY_FBSTRING_HASH
#undef FOLLY_FBSTRING_HASH1
FOLLY_POP_WARNING
#undef FBSTRING_DISABLE_SSO
namespace folly {
template <class T>
struct IsSomeString;
template <>
struct IsSomeString<fbstring> : std::true_type {};
} // namespace folly