omni/String.h

File members: omni/String.h

// Copyright (c) 2021-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once

#include "../carb/Defines.h"
#include "detail/PointerIterator.h"
#include "../carb/cpp/StringView.h"

#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <istream>
#include <iterator>
#include <limits>
#include <memory>
#include <ostream>
#include <string>
#include <type_traits>

#if CARB_HAS_CPP17
#    include <string_view>
#endif

#if CARB_HAS_CPP20
#    include <version>
#    if defined __cpp_lib_constexpr_string && __cpp_lib_constexpr_string >= 201907L
#        define CARBLOCAL_STDSTRING_CONSTEXPR constexpr
#    else
#        define CARBLOCAL_STDSTRING_CONSTEXPR
#    endif
#    if defined __cpp_constexpr && __cpp_constexpr >= 202002L
#        define CARBLOCAL_UNION_CONSTEXPR constexpr
#    else
#        define CARBLOCAL_UNION_CONSTEXPR
#    endif
#else
#    define CARBLOCAL_STDSTRING_CONSTEXPR
#    define CARBLOCAL_UNION_CONSTEXPR
#endif

CARB_IGNOREWARNING_MSC_WITH_PUSH(4201) // nonstandard extension used: nameless struct/union.

namespace omni
{

struct formatted_t
{
};

constexpr formatted_t formatted{};

struct vformatted_t
{
};

constexpr vformatted_t vformatted{};

#ifndef DOXYGEN_SHOULD_SKIP_THIS
namespace detail
{

struct char_traits
{
    static constexpr void assign(char& dest, const char& c) noexcept;

    static constexpr char* assign(char* dest, std::size_t count, char c) noexcept;

    static constexpr void move(char* dest, const char* source, std::size_t count) noexcept;

    static constexpr void copy(char* dest, const char* source, std::size_t count) noexcept;

    static constexpr int compare(const char* s1, const char* s2, std::size_t count) noexcept;

    static constexpr std::size_t length(const char* s) noexcept;

    static constexpr const char* find(const char* s, std::size_t count, char ch) noexcept;
};

#    if CARB_HAS_CPP17
template <typename T, typename Res = void>
using is_sv_convertible =
    std::enable_if_t<std::conjunction<std::is_convertible<const T&, std::string_view>,
                                      std::negation<std::is_convertible<const T&, const char*>>>::value,
                     Res>;
#    endif
} // namespace detail
#endif

// Handle case where Windows.h may have defined 'max'
#pragma push_macro("max")
#undef max

class CARB_VIZ string
{

public:
#if CARB_HAS_CPP20 && defined __cpp_lib_constexpr_string && __cpp_lib_constexpr_string >= 201907L
    using traits_type = std::char_traits<char>;
#else
    using traits_type = detail::char_traits;
#endif
    using value_type = char;

    using size_type = std::size_t;
    using reference = value_type&;
    using const_reference = const value_type&;
    using pointer = value_type*;
    using const_pointer = const value_type*;
    using difference_type = std::pointer_traits<pointer>::difference_type;

    using iterator = detail::PointerIterator<pointer, string>;
    using const_iterator = detail::PointerIterator<const_pointer, string>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
    using reverse_iterator = std::reverse_iterator<iterator>;

    static constexpr size_type npos = std::numeric_limits<size_type>::max();

    string() noexcept;

    string(size_type n, value_type c);

    string(const string& str, size_type pos);

    string(const string& str, size_type pos, size_type n);

    string(const value_type* s, size_type n);

    string(const value_type* s);

    template <typename InputIterator>
    string(InputIterator begin, InputIterator end);

    string(const string& str);

    string(string&& str) noexcept;

    string(std::initializer_list<value_type> ilist);

    template <class... Args>
    string(formatted_t, const char* fmt, Args&&... args);

    string(vformatted_t, const char* fmt, va_list ap);

    explicit string(const std::string& str);

    string(const std::string& str, size_type pos, size_type n);

    explicit string(const carb::cpp::string_view& sv);

    string(const carb::cpp::string_view& sv, size_type pos, size_type n);

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    explicit string(const T& t);

    template <typename T, typename = detail::is_sv_convertible<T>>
    string(const T& t, size_type pos, size_type n);
#endif

    string(std::nullptr_t) = delete;

    ~string() noexcept;

    string& operator=(const string& str);

    string& operator=(string&& str) noexcept;

    string& operator=(const value_type* s);

    string& operator=(value_type c);

    string& operator=(std::initializer_list<value_type> ilist);

    string& operator=(const std::string& str);

    string& operator=(const carb::cpp::string_view& sv);

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    string& operator=(const T& t);
#endif

    string& operator=(std::nullptr_t) = delete;

    string& assign(size_type n, value_type c);

    string& assign(const string& str);

    string& assign(const string& str, size_type pos, size_type n = npos);

    string& assign(string&& str);

    string& assign(const value_type* s, size_type n);

    string& assign(const value_type* s);

    template <class InputIterator>
    string& assign(InputIterator first, InputIterator last);

    string& assign(std::initializer_list<value_type> ilist);

    string& assign(const std::string& str);

    string& assign(const std::string& str, size_type pos, size_type n = npos);

    string& assign(const carb::cpp::string_view& sv);

    string& assign(const carb::cpp::string_view& sv, size_type pos, size_type n = npos);

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    string& assign(const T& t);

    template <typename T, typename = detail::is_sv_convertible<T>>
    string& assign(const T& t, size_type pos, size_type n = npos);
#endif

    string& assign_printf(const char* fmt, ...) CARB_PRINTF_FUNCTION(2, 3);

    string& assign_vprintf(const char* fmt, va_list ap);

    constexpr reference at(size_type pos);

    constexpr const_reference at(size_type pos) const;

    constexpr reference operator[](size_type pos);

    constexpr const_reference operator[](size_type pos) const;

    constexpr reference front();

    constexpr const_reference front() const;

    constexpr reference back();

    constexpr const_reference back() const;

    constexpr const value_type* data() const noexcept;

    constexpr value_type* data() noexcept;

    constexpr const value_type* c_str() const noexcept;

    constexpr operator carb::cpp::string_view() const noexcept;

#if CARB_HAS_CPP17
    constexpr operator std::string_view() const noexcept;
#endif

    constexpr iterator begin() noexcept;

    constexpr const_iterator begin() const noexcept;

    constexpr const_iterator cbegin() const noexcept;

    constexpr iterator end() noexcept;

    constexpr const_iterator end() const noexcept;

    constexpr const_iterator cend() const noexcept;

    reverse_iterator rbegin() noexcept;

    const_reverse_iterator rbegin() const noexcept;

    const_reverse_iterator crbegin() const noexcept;

    reverse_iterator rend() noexcept;

    const_reverse_iterator rend() const noexcept;

    const_reverse_iterator crend() const noexcept;

    constexpr bool empty() const noexcept;

    constexpr size_type size() const noexcept;

    constexpr size_type length() const noexcept;

    constexpr size_type max_size() const noexcept;

    void reserve(size_type new_cap);

    void reserve(); // deprecated in C++20

    constexpr size_type capacity() const noexcept;

    void shrink_to_fit();

    constexpr void clear() noexcept;

    string& insert(size_type pos, size_type n, value_type c);

    string& insert(size_type pos, const value_type* s);

    string& insert(size_type pos, const value_type* s, size_type n);

    string& insert(size_type pos, const string& str);

    string& insert(size_type pos1, const string& str, size_type pos2, size_type n = npos);

    iterator insert(const_iterator p, value_type c);

    iterator insert(const_iterator p, size_type n, value_type c);

    template <class InputIterator>
    iterator insert(const_iterator p, InputIterator first, InputIterator last);

    iterator insert(const_iterator p, std::initializer_list<value_type> ilist);

    string& insert(size_type pos, const std::string& str);

    string& insert(size_type pos1, const std::string& str, size_type pos2, size_type n = npos);

    string& insert(size_type pos, const carb::cpp::string_view& sv);

    string& insert(size_type pos1, const carb::cpp::string_view& sv, size_type pos2, size_type n = npos);

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    string& insert(size_type pos, const T& t);

    template <typename T, typename = detail::is_sv_convertible<T>>
    string& insert(size_type pos1, const T& t, size_type pos2, size_type n = npos);
#endif

    string& insert_printf(size_type pos, const char* fmt, ...) CARB_PRINTF_FUNCTION(3, 4);

    string& insert_vprintf(size_type pos, const char* fmt, va_list ap);

    iterator insert_printf(const_iterator p, const char* fmt, ...) CARB_PRINTF_FUNCTION(3, 4);

    iterator insert_vprintf(const_iterator p, const char* fmt, va_list ap);

    constexpr string& erase(size_type pos = 0, size_type n = npos);

    constexpr iterator erase(const_iterator pos);

    constexpr iterator erase(const_iterator first, const_iterator last);

    void push_back(value_type c);

    constexpr void pop_back();

    string& append(size_type n, value_type c);

    string& append(const string& str);

    string& append(const string& str, size_type pos, size_type n = npos);

    string& append(const value_type* s, size_type n);

    string& append(const value_type* s);

    template <class InputIterator>
    string& append(InputIterator first, InputIterator last);

    string& append(std::initializer_list<value_type> ilist);

    string& append(const std::string& str);

    string& append(const std::string& str, size_type pos, size_type n = npos);

    string& append(const carb::cpp::string_view& sv);

    string& append(const carb::cpp::string_view& sv, size_type pos, size_type n = npos);

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    string& append(const T& t);

    template <typename T, typename = detail::is_sv_convertible<T>>
    string& append(const T& t, size_type pos, size_type n = npos);
#endif

    string& append_printf(const char* fmt, ...) CARB_PRINTF_FUNCTION(2, 3);

    string& append_vprintf(const char* fmt, va_list ap);

    string& operator+=(const string& str);

    string& operator+=(value_type c);

    string& operator+=(const value_type* s);

    string& operator+=(std::initializer_list<value_type> ilist);

    string& operator+=(const std::string& str);

    string& operator+=(const carb::cpp::string_view& sv);

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    string& operator+=(const T& t);
#endif

    constexpr int compare(const string& str) const noexcept;

    constexpr int compare(size_type pos1, size_type n1, const string& str) const;

    constexpr int compare(size_type pos1, size_type n1, const string& str, size_type pos2, size_type n2 = npos) const;

    constexpr int compare(const value_type* s) const;

    constexpr int compare(size_type pos1, size_type n1, const value_type* s) const;

    constexpr int compare(size_type pos1, size_type n1, const value_type* s, size_type n2) const;

    CARBLOCAL_STDSTRING_CONSTEXPR int compare(const std::string& str) const noexcept;

    CARBLOCAL_STDSTRING_CONSTEXPR int compare(size_type pos1, size_type n1, const std::string& str) const;

    CARBLOCAL_STDSTRING_CONSTEXPR int compare(
        size_type pos1, size_type n1, const std::string& str, size_type pos2, size_type n2 = npos) const;

    constexpr int compare(const carb::cpp::string_view& sv) const noexcept;

    constexpr int compare(size_type pos1, size_type n1, const carb::cpp::string_view& sv) const;

    constexpr int compare(
        size_type pos1, size_type n1, const carb::cpp::string_view& sv, size_type pos2, size_type n2 = npos) const;

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    constexpr int compare(const T& t) const noexcept;

    template <typename T, typename = detail::is_sv_convertible<T>>
    constexpr int compare(size_type pos1, size_type n1, const T& t) const;

    template <typename T, typename = detail::is_sv_convertible<T>>
    constexpr int compare(size_type pos1, size_type n1, const T& t, size_type pos2, size_type n2 = npos) const;
#endif

    constexpr bool starts_with(value_type c) const noexcept;

    constexpr bool starts_with(const_pointer s) const;

    constexpr bool starts_with(carb::cpp::string_view sv) const noexcept;

#if CARB_HAS_CPP17
    constexpr bool starts_with(std::string_view sv) const noexcept;
#endif

    constexpr bool ends_with(value_type c) const noexcept;

    constexpr bool ends_with(const_pointer s) const;

    constexpr bool ends_with(carb::cpp::string_view sv) const noexcept;

#if CARB_HAS_CPP17
    constexpr bool ends_with(std::string_view sv) const noexcept;
#endif

    constexpr bool contains(value_type c) const noexcept;

    constexpr bool contains(const_pointer s) const;

    constexpr bool contains(carb::cpp::string_view sv) const noexcept;

#if CARB_HAS_CPP17
    constexpr bool contains(std::string_view sv) const noexcept;
#endif

    string& replace(size_type pos1, size_type n1, const string& str);

    string& replace(size_type pos1, size_type n1, const string& str, size_type pos2, size_type n2 = npos);

    template <class InputIterator>
    string& replace(const_iterator i1, const_iterator i2, InputIterator j1, InputIterator j2);

    string& replace(size_type pos, size_type n1, const value_type* s, size_type n2);

    string& replace(size_type pos, size_type n1, const value_type* s);

    string& replace(size_type pos, size_type n1, size_type n2, value_type c);

    string& replace(size_type pos1, size_type n1, const std::string& str);

    string& replace(size_type pos1, size_type n1, const std::string& str, size_type pos2, size_type n2 = npos);

    string& replace(size_type pos1, size_type n1, const carb::cpp::string_view& sv);

    string& replace(size_type pos1, size_type n1, const carb::cpp::string_view& sv, size_type pos2, size_type n2 = npos);

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    string& replace(size_type pos1, size_type n1, const T& t);

    template <typename T, typename = detail::is_sv_convertible<T>>
    string& replace(size_type pos1, size_type n1, const T& t, size_type pos2, size_type n2);
#endif

    string& replace(const_iterator i1, const_iterator i2, const string& str);

    string& replace(const_iterator i1, const_iterator i2, const value_type* s, size_type n);

    string& replace(const_iterator i1, const_iterator i2, const value_type* s);

    string& replace(const_iterator i1, const_iterator i2, size_type n, value_type c);

    string& replace(const_iterator i1, const_iterator i2, std::initializer_list<value_type> ilist);

    string& replace(const_iterator i1, const_iterator i2, const std::string& str);

    string& replace(const_iterator i1, const_iterator i2, const carb::cpp::string_view& sv);

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    string& replace(const_iterator i1, const_iterator i2, const T& t);
#endif

    string& replace_format(size_type pos, size_type n1, const value_type* fmt, ...) CARB_PRINTF_FUNCTION(4, 5);

    string& replace_vformat(size_type pos, size_type n1, const value_type* fmt, va_list ap);

    string& replace_format(const_iterator i1, const_iterator i2, const value_type* fmt, ...) CARB_PRINTF_FUNCTION(4, 5);

    string& replace_vformat(const_iterator i1, const_iterator i2, const value_type* fmt, va_list ap);

    string substr(size_type pos = 0, size_type n = npos) const;

    constexpr size_type copy(value_type* s, size_type n, size_type pos = 0) const;

    void resize(size_type n, value_type c);

    void resize(size_type n);

    void swap(string& str) noexcept;

    constexpr size_type find(const string& str, size_type pos = 0) const noexcept;

    constexpr size_type find(const value_type* s, size_type pos, size_type n) const;

    constexpr size_type find(const value_type* s, size_type pos = 0) const;

    CARBLOCAL_STDSTRING_CONSTEXPR size_type find(const std::string& str, size_type pos = 0) const noexcept;

    constexpr size_type find(const carb::cpp::string_view& sv, size_type pos = 0) const noexcept;

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    constexpr size_type find(const T& t, size_type pos = 0) const noexcept;
#endif

    constexpr size_type find(value_type c, size_type pos = 0) const noexcept;

    constexpr size_type rfind(const string& str, size_type pos = npos) const noexcept;

    constexpr size_type rfind(const value_type* s, size_type pos, size_type n) const;

    constexpr size_type rfind(const value_type* s, size_type pos = npos) const;

    constexpr size_type rfind(value_type c, size_type pos = npos) const noexcept;

    CARBLOCAL_STDSTRING_CONSTEXPR size_type rfind(const std::string& str, size_type pos = npos) const noexcept;

    constexpr size_type rfind(const carb::cpp::string_view& sv, size_type pos = npos) const noexcept;

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    constexpr size_type rfind(const T& t, size_type pos = npos) const noexcept;
#endif

    constexpr size_type find_first_of(const string& str, size_type pos = 0) const noexcept;

    constexpr size_type find_first_of(const value_type* s, size_type pos, size_type n) const;

    constexpr size_type find_first_of(const value_type* s, size_type pos = 0) const;

    constexpr size_type find_first_of(value_type c, size_type pos = 0) const noexcept;

    CARBLOCAL_STDSTRING_CONSTEXPR size_type find_first_of(const std::string& str, size_type pos = 0) const noexcept;

    constexpr size_type find_first_of(const carb::cpp::string_view& sv, size_type pos = 0) const noexcept;

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    constexpr size_type find_first_of(const T& t, size_type pos = 0) const noexcept;
#endif

    constexpr size_type find_last_of(const string& str, size_type pos = npos) const noexcept;

    constexpr size_type find_last_of(const value_type* s, size_type pos, size_type n) const;

    constexpr size_type find_last_of(const value_type* s, size_type pos = npos) const;

    constexpr size_type find_last_of(value_type c, size_type pos = npos) const noexcept;

    CARBLOCAL_STDSTRING_CONSTEXPR size_type find_last_of(const std::string& str, size_type pos = npos) const noexcept;

    constexpr size_type find_last_of(const carb::cpp::string_view& sv, size_type pos = npos) const noexcept;

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    constexpr size_type find_last_of(const T& t, size_type pos = npos) const noexcept;
#endif

    constexpr size_type find_first_not_of(const string& str, size_type pos = 0) const noexcept;

    constexpr size_type find_first_not_of(const value_type* s, size_type pos, size_type n) const;

    constexpr size_type find_first_not_of(const value_type* s, size_type pos = 0) const;

    constexpr size_type find_first_not_of(value_type c, size_type pos = 0) const noexcept;

    CARBLOCAL_STDSTRING_CONSTEXPR size_type find_first_not_of(const std::string& str, size_type pos = 0) const noexcept;

    constexpr size_type find_first_not_of(const carb::cpp::string_view& sv, size_type pos = 0) const noexcept;

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    constexpr size_type find_first_not_of(const T& t, size_type pos = 0) const noexcept;
#endif

    constexpr size_type find_last_not_of(const string& str, size_type pos = npos) const noexcept;

    constexpr size_type find_last_not_of(const value_type* s, size_type pos, size_type n) const;

    constexpr size_type find_last_not_of(const value_type* s, size_type pos = npos) const;

    constexpr size_type find_last_not_of(value_type c, size_type pos = npos) const noexcept;

    CARBLOCAL_STDSTRING_CONSTEXPR size_type find_last_not_of(const std::string& str, size_type pos = npos) const noexcept;

    constexpr size_type find_last_not_of(const carb::cpp::string_view& sv, size_type pos = npos) const noexcept;

#if CARB_HAS_CPP17
    template <typename T, typename = detail::is_sv_convertible<T>>
    constexpr size_type find_last_not_of(const T& t, size_type pos = npos) const noexcept;
#endif

private:
    // Size of the character buffer for small string optimization
    constexpr static size_type kSMALL_STRING_SIZE = 32;

    // The last byte of the SSO buffer contains the remaining size in the buffer
    constexpr static size_type kSMALL_SIZE_OFFSET = kSMALL_STRING_SIZE - 1;

    // Sentinel value indicating that the string is using heap allocated storage
    constexpr static value_type kSTRING_IS_ALLOCATED = std::numeric_limits<char>::max();

    // Struct that holds the data for an allocated string. This could be an anonymous struct inside the union,
    // however naming it outside the union allows for the static_asserts below for ABI safety.
    struct allocated_data
    {
        CARB_VIZ pointer m_ptr;
        CARB_VIZ size_type m_size;
        CARB_VIZ size_type m_capacity;
    };

    union
    {
        CARB_VIZ allocated_data m_allocated_data;

        CARB_VIZ value_type m_local_data[kSMALL_STRING_SIZE];
    };

    static_assert(kSMALL_STRING_SIZE == 32, "ABI-safety: Cannot change the small string optimization size");
    static_assert(size_type(kSTRING_IS_ALLOCATED) >= kSMALL_STRING_SIZE,
                  "Invalid assumption: Sentinel Value must be greater than max small string size.");
    static_assert(sizeof(allocated_data) == 24, "ABI-safety: Cannot change allocated data size");
    static_assert(offsetof(allocated_data, m_ptr) == 0, "ABI-safety: Member offset cannot change");
    static_assert(offsetof(allocated_data, m_size) == 8, "ABI-safety: Member offset cannot change");
    static_assert(offsetof(allocated_data, m_capacity) == 16, "ABI-safety: Member offset cannot change");
    static_assert(sizeof(allocated_data) < kSMALL_STRING_SIZE,
                  "Invalid assumption: sizeof(allocated_data) must be less than the small string size.");

    // Helper functions
    constexpr bool is_local() const;

    CARBLOCAL_UNION_CONSTEXPR void set_local(size_type new_size) noexcept;

    CARBLOCAL_UNION_CONSTEXPR void set_allocated() noexcept;

    constexpr reference get_reference(size_type pos) noexcept;

    constexpr const_reference get_reference(size_type pos) const noexcept;

    constexpr pointer get_pointer(size_type pos) noexcept;

    constexpr const_pointer get_pointer(size_type pos) const noexcept;

    CARBLOCAL_UNION_CONSTEXPR void set_empty() noexcept;

    constexpr void range_check(size_type pos, size_type size, const char* function) const;

    // Checks that pos is within the range [begin(), end()]
    constexpr void range_check(const_iterator pos, const char* function) const;

    // Checks that first is within the range [begin(), end()], that last is within the range [begin(), end()], and that
    // first <= last.
    constexpr void range_check(const_iterator first, const_iterator last, const char* function) const;

    // Checks if current+n > max_size(). If it is not, returns current+n.
    constexpr size_type length_check(size_type current, size_type n, const char* function) const;

    constexpr void set_size(size_type new_size) noexcept;

    constexpr bool should_allocate(size_type n) const noexcept;

    constexpr bool overlaps_this_string(const_pointer s) const noexcept;

    void overlap_check(const_pointer s) const;

    // Calls std::vsnprintf, but throws if it fails
    size_type vsnprintf_check(char* buffer, size_type buffer_size, const char* format, va_list args);
    template <class... Args>
    size_type snprintf_check(char* buffer, size_type buffer_size, const char* format, Args&&... args);

    pointer allocate_if_necessary(size_type size);

    void initialize(const_pointer src, size_type size);

    template <typename InputIterator>
    void initialize(InputIterator begin, InputIterator end, size_type size);

    void dispose();

    pointer allocate_buffer(size_type old_capacity, size_type& new_capacity);

    pointer grow_buffer_to(size_type new_capacity);

    // Grows a buffer to new_size, fills it with the data from the three provided pointers, and swaps the new buffer
    // for the old one. This is used in functions like insert and replace, which may need to fill the new buffer with
    // characters from multiple locations.
    void grow_buffer_and_fill(
        size_type new_size, const_pointer p1, size_type s1, const_pointer p2, size_type s2, const_pointer p3, size_type s3);

    template <class InputIterator>
    void grow_buffer_and_append(size_type new_size, InputIterator first, InputIterator last);

    // Internal implementations
    string& assign_internal(const_pointer src, size_type new_size);

    template <typename InputIterator>
    string& assign_internal(InputIterator begin, InputIterator end, size_type new_size);

    string& insert_internal(size_type pos, value_type c, size_type n);

    string& insert_internal(size_type pos, const_pointer src, size_type n);

    string& append_internal(const_pointer src, size_type n);

    constexpr int compare_internal(const_pointer this_str,
                                   const_pointer other_str,
                                   size_type this_size,
                                   size_type other_size) const noexcept;

    void replace_setup(size_type pos, size_type replaced_size, size_type replacement_size);
};

static_assert(std::is_standard_layout<string>::value, "string must be standard layout"); // Not interop-safe because not
                                                                                         // trivially copyable
static_assert(sizeof(string) == 32, "ABI Safety: String must be 32 bytes");

string operator+(const string& lhs, const string& rhs);

string operator+(const string& lhs, const char* rhs);

string operator+(const string& lhs, char rhs);

string operator+(const string& lhs, const std::string& rhs);

string operator+(const char* lhs, const string& rhs);

string operator+(char lhs, const string& rhs);

string operator+(const std::string& lhs, const string& rhs);

string operator+(string&& lhs, string&& rhs);

string operator+(string&& lhs, const string& rhs);

string operator+(string&& lhs, const char* rhs);

string operator+(string&& lhs, char rhs);

string operator+(string&& lhs, const string& rhs);

string operator+(const string& lhs, string&& rhs);

string operator+(const char* lhs, string&& rhs);

string operator+(char lhs, string&& rhs);

string operator+(const std::string& lhs, string&& rhs);

constexpr bool operator==(const string& lhs, const string& rhs) noexcept;

constexpr bool operator==(const string& lhs, const char* rhs);

constexpr bool operator==(const char* lhs, const string& rhs);

CARBLOCAL_STDSTRING_CONSTEXPR bool operator==(const string& lhs, const std::string& rhs) noexcept;

CARBLOCAL_STDSTRING_CONSTEXPR bool operator==(const std::string& lhs, const string& rhs) noexcept;

constexpr bool operator!=(const string& lhs, const string& rhs) noexcept;

constexpr bool operator!=(const string& lhs, const char* rhs);

constexpr bool operator!=(const char* lhs, const string& rhs);

CARBLOCAL_STDSTRING_CONSTEXPR bool operator!=(const string& lhs, const std::string& rhs) noexcept;

CARBLOCAL_STDSTRING_CONSTEXPR bool operator!=(const std::string& lhs, const string& rhs) noexcept;

constexpr bool operator<(const string& lhs, const string& rhs) noexcept;

constexpr bool operator<(const string& lhs, const char* rhs);

constexpr bool operator<(const char* lhs, const string& rhs);

CARBLOCAL_STDSTRING_CONSTEXPR bool operator<(const string& lhs, const std::string& rhs) noexcept;

CARBLOCAL_STDSTRING_CONSTEXPR bool operator<(const std::string& lhs, const string& rhs) noexcept;

constexpr bool operator<=(const string& lhs, const string& rhs) noexcept;

constexpr bool operator<=(const string& lhs, const char* rhs);

constexpr bool operator<=(const char* lhs, const string& rhs);

CARBLOCAL_STDSTRING_CONSTEXPR bool operator<=(const string& lhs, const std::string& rhs) noexcept;

CARBLOCAL_STDSTRING_CONSTEXPR bool operator<=(const std::string& lhs, const string& rhs) noexcept;

constexpr bool operator>(const string& lhs, const string& rhs) noexcept;

constexpr bool operator>(const string& lhs, const char* rhs);

constexpr bool operator>(const char* lhs, const string& rhs);

CARBLOCAL_STDSTRING_CONSTEXPR bool operator>(const string& lhs, const std::string& rhs) noexcept;

CARBLOCAL_STDSTRING_CONSTEXPR bool operator>(const std::string& lhs, const string& rhs) noexcept;

constexpr bool operator>=(const string& lhs, const string& rhs) noexcept;

constexpr bool operator>=(const string& lhs, const char* rhs);

constexpr bool operator>=(const char* lhs, const string& rhs);

CARBLOCAL_STDSTRING_CONSTEXPR bool operator>=(const string& lhs, const std::string& rhs) noexcept;

CARBLOCAL_STDSTRING_CONSTEXPR bool operator>=(const std::string& lhs, const string& rhs) noexcept;

void swap(string& lhs, string& rhs) noexcept;

template <typename U>
CARB_CPP20_CONSTEXPR string::size_type erase(string& str, const U& val);

template <typename Pred>
CARB_CPP20_CONSTEXPR string::size_type erase_if(string& str, Pred pred);

std::basic_ostream<char, std::char_traits<char>>& operator<<(std::basic_ostream<char, std::char_traits<char>>& os,
                                                             const string& str);

std::basic_istream<char, std::char_traits<char>>& operator>>(std::basic_istream<char, std::char_traits<char>>& is,
                                                             string& str);

std::basic_istream<char, std::char_traits<char>>& getline(std::basic_istream<char, std::char_traits<char>>&& input,
                                                          string& str,
                                                          char delim);

std::basic_istream<char, std::char_traits<char>>& getline(std::basic_istream<char, std::char_traits<char>>&& input,
                                                          string& str);

int stoi(const string& str, std::size_t* pos = nullptr, int base = 10);

long stol(const string& str, std::size_t* pos = nullptr, int base = 10);

long long stoll(const string& str, std::size_t* pos = nullptr, int base = 10);

unsigned long stoul(const string& str, std::size_t* pos = nullptr, int base = 10);

unsigned long long stoull(const string& str, std::size_t* pos = nullptr, int base = 10);

float stof(const string& str, std::size_t* pos = nullptr);

double stod(const string& str, std::size_t* pos = nullptr);

long double stold(const string& str, std::size_t* pos = nullptr);

string to_string(int value);

string to_string(long value);

string to_string(long long value);

string to_string(unsigned value);

string to_string(unsigned long value);

string to_string(unsigned long long value);

string to_string(float value);

string to_string(double value);

string to_string(long double value);

} // namespace omni

namespace std
{

template <>
struct hash<omni::string>
{
    using argument_type = omni::string;
    using result_type = std::size_t;
    size_t operator()(const argument_type& x) const noexcept
    {
        return carb::hashBuffer(x.data(), x.size());
    }
};
} // namespace std

#include "String.inl"

#pragma pop_macro("max")

CARB_IGNOREWARNING_MSC_POP
#undef CARBLOCAL_STDSTRING_CONSTEXPR
#undef CARBLOCAL_UNION_CONSTEXPR