// Copyright (C) 2022 The Qt Company Ltd. // Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QANYSTRINGVIEW_H #define QANYSTRINGVIEW_H #include #include #include #include #ifdef __cpp_impl_three_way_comparison #include #endif #include #include class tst_QAnyStringView; QT_BEGIN_NAMESPACE namespace QtPrivate { template struct wrapped { using type = Result; }; template using wrapped_t = typename wrapped::type; } // namespace QtPrivate class QAnyStringView { public: typedef qptrdiff difference_type; typedef qsizetype size_type; private: static constexpr size_t SizeMask = (std::numeric_limits::max)() / 4; #if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) || defined(QT_BOOTSTRAPPED) static constexpr int SizeShift = 2; static constexpr size_t Latin1Flag = 1; #else static constexpr int SizeShift = 0; static constexpr size_t Latin1Flag = SizeMask + 1; #endif static constexpr size_t TwoByteCodePointFlag = Latin1Flag << 1; static constexpr size_t TypeMask = ~(SizeMask << SizeShift); static_assert(TypeMask == (Latin1Flag|TwoByteCodePointFlag)); // Tag bits // 0 0 Utf8 // 0 1 Latin1 // 1 0 Utf16 // 1 1 Unused // ^ ^ latin1 // | sizeof code-point == 2 enum Tag : size_t { Utf8 = 0, Latin1 = Latin1Flag, Utf16 = TwoByteCodePointFlag, Unused = TypeMask, }; template using if_compatible_char = std::enable_if_t, QtPrivate::IsCompatibleChar8Type >, bool>; template using if_compatible_pointer = std::enable_if_t, QtPrivate::IsCompatiblePointer8 >, bool>; template using if_compatible_container = std::enable_if_t, QtPrivate::IsContainerCompatibleWithQUtf8StringView >, bool>; template using if_convertible_to = std::enable_if_t, QAnyStringView::Tag>, std::is_same, QAnyStringView>, // don't make a copy/move ctor std::is_pointer>, // const char*, etc std::is_same, QByteArray>, std::is_same, QString> >>, // this is what we're really after: std::is_convertible >, bool>; // confirm we don't make an accidental copy constructor: static_assert(QtPrivate::IsContainerCompatibleWithQStringView::value == false); static_assert(QtPrivate::IsContainerCompatibleWithQUtf8StringView::value == false); template static constexpr bool isAsciiOnlyCharsAtCompileTime(Char *str, qsizetype sz) noexcept { // do not perform check if not at compile time if (!q20::is_constant_evaluated()) return false; if constexpr (sizeof(Char) != sizeof(char)) { Q_UNUSED(str); Q_UNUSED(sz); return false; } else { for (qsizetype i = 0; i < sz; ++i) { if (uchar(str[i]) > 0x7f) return false; } return true; } } template static constexpr std::size_t encodeType(const Char *str, qsizetype sz) noexcept { // Utf16 if 16 bit, Latin1 if ASCII, else Utf8 Q_ASSERT(sz >= 0); Q_ASSERT(sz <= qsizetype(SizeMask)); Q_ASSERT(str || !sz); return (std::size_t(sz) << SizeShift) | uint(sizeof(Char) == sizeof(char16_t)) * Tag::Utf16 | uint(isAsciiOnlyCharsAtCompileTime(str, sz)) * Tag::Latin1; } template static constexpr qsizetype lengthHelperPointer(const Char *str) noexcept { if (q20::is_constant_evaluated()) return qsizetype(std::char_traits::length(str)); if constexpr (sizeof(Char) == sizeof(char16_t)) return QtPrivate::qustrlen(reinterpret_cast(str)); else return qsizetype(strlen(reinterpret_cast(str))); } static QChar toQChar(char ch) noexcept { return toQChar(QLatin1Char{ch}); } // we don't handle UTF-8 multibytes static QChar toQChar(QChar ch) noexcept { return ch; } static QChar toQChar(QLatin1Char ch) noexcept { return ch; } explicit constexpr QAnyStringView(const void *d, qsizetype n, std::size_t sizeAndType) noexcept : m_data{d}, m_size{std::size_t(n) | (sizeAndType & TypeMask)} {} public: constexpr QAnyStringView() noexcept : m_data{nullptr}, m_size{0} {} constexpr QAnyStringView(std::nullptr_t) noexcept : QAnyStringView() {} template = true> constexpr QAnyStringView(const Char *str, qsizetype len) : m_data{str}, m_size{encodeType(str, len)} { } template = true> constexpr QAnyStringView(const Char *f, const Char *l) : QAnyStringView(f, l - f) {} #ifdef Q_QDOC template constexpr QAnyStringView(const Char (&array)[N]) noexcept; template constexpr QAnyStringView(const Char *str) noexcept; #else template = true> constexpr QAnyStringView(const Pointer &str) noexcept : QAnyStringView{str, str ? lengthHelperPointer(str) : 0} {} #endif // defined in qstring.h inline QAnyStringView(const QByteArray &str) noexcept; // TODO: Should we have this at all? Remove? inline QAnyStringView(const QString &str) noexcept; inline constexpr QAnyStringView(QLatin1StringView str) noexcept; template = true> constexpr Q_ALWAYS_INLINE QAnyStringView(const Container &c) noexcept : QAnyStringView(std::data(c), QtPrivate::lengthHelperContainer(c)) {} template = true> constexpr QAnyStringView(Container &&c, QtPrivate::wrapped_t &&capacity = {}) //noexcept(std::is_nothrow_constructible_v) : QAnyStringView(capacity = std::forward(c)) {} template = true> constexpr QAnyStringView(Container &&c, QtPrivate::wrapped_t &&capacity = {}) //noexcept(std::is_nothrow_constructible_v) : QAnyStringView(capacity = std::forward(c)) {} template = true> constexpr QAnyStringView(const Char &c) noexcept : QAnyStringView{&c, 1} {} constexpr QAnyStringView(const QChar &c) noexcept : QAnyStringView{&c, 1} {} template , bool> = true> constexpr QAnyStringView(Char c, Container &&capacity = {}) : QAnyStringView(capacity = QChar::fromUcs4(c)) {} constexpr QAnyStringView(QStringView v) noexcept : QAnyStringView(std::data(v), QtPrivate::lengthHelperContainer(v)) {} template constexpr QAnyStringView(QBasicUtf8StringView v) noexcept : QAnyStringView(std::data(v), QtPrivate::lengthHelperContainer(v)) {} template = true> [[nodiscard]] constexpr static QAnyStringView fromArray(const Char (&string)[Size]) noexcept { return QAnyStringView(string, Size); } // defined in qstring.h: template inline constexpr decltype(auto) visit(Visitor &&v) const; [[nodiscard]] constexpr QAnyStringView mid(qsizetype pos, qsizetype n = -1) const { using namespace QtPrivate; auto result = QContainerImplHelper::mid(size(), &pos, &n); return result == QContainerImplHelper::Null ? QAnyStringView() : sliced(pos, n); } [[nodiscard]] constexpr QAnyStringView left(qsizetype n) const { if (size_t(n) >= size_t(size())) n = size(); return sliced(0, n); } [[nodiscard]] constexpr QAnyStringView right(qsizetype n) const { if (size_t(n) >= size_t(size())) n = size(); return sliced(size() - n, n); } [[nodiscard]] constexpr QAnyStringView sliced(qsizetype pos) const { verify(pos, 0); auto r = *this; r.advanceData(pos); r.setSize(size() - pos); return r; } [[nodiscard]] constexpr QAnyStringView sliced(qsizetype pos, qsizetype n) const { verify(pos, n); auto r = *this; r.advanceData(pos); r.setSize(n); return r; } [[nodiscard]] constexpr QAnyStringView first(qsizetype n) const { verify(0, n); return sliced(0, n); } [[nodiscard]] constexpr QAnyStringView last(qsizetype n) const { verify(0, n); return sliced(size() - n, n); } [[nodiscard]] constexpr QAnyStringView chopped(qsizetype n) const { verify(0, n); return sliced(0, size() - n); } constexpr void truncate(qsizetype n) { verify(0, n); setSize(n); } constexpr void chop(qsizetype n) { verify(0, n); setSize(size() - n); } [[nodiscard]] inline QString toString() const; // defined in qstring.h [[nodiscard]] constexpr qsizetype size() const noexcept { return qsizetype((m_size >> SizeShift) & SizeMask); } [[nodiscard]] constexpr const void *data() const noexcept { return m_data; } [[nodiscard]] Q_CORE_EXPORT static int compare(QAnyStringView lhs, QAnyStringView rhs, Qt::CaseSensitivity cs = Qt::CaseSensitive) noexcept; [[nodiscard]] Q_CORE_EXPORT static bool equal(QAnyStringView lhs, QAnyStringView rhs) noexcept; static constexpr inline bool detects_US_ASCII_at_compile_time = #ifdef QT_SUPPORTS_IS_CONSTANT_EVALUATED true #else false #endif ; // // STL compatibility API: // [[nodiscard]] constexpr QChar front() const; // NOT noexcept! [[nodiscard]] constexpr QChar back() const; // NOT noexcept! [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } [[nodiscard]] constexpr qsizetype size_bytes() const noexcept { return size() * charSize(); } // // Qt compatibility API: // [[nodiscard]] constexpr bool isNull() const noexcept { return !m_data; } [[nodiscard]] constexpr bool isEmpty() const noexcept { return empty(); } [[nodiscard]] constexpr qsizetype length() const noexcept { return size(); } private: friend bool comparesEqual(const QAnyStringView &lhs, const QAnyStringView &rhs) noexcept { return QAnyStringView::equal(lhs, rhs); } friend Qt::strong_ordering compareThreeWay(const QAnyStringView &lhs, const QAnyStringView &rhs) noexcept { const int res = QAnyStringView::compare(lhs, rhs); return Qt::compareThreeWay(res, 0); } Q_DECLARE_STRONGLY_ORDERED(QAnyStringView) #ifndef QT_NO_DEBUG_STREAM Q_CORE_EXPORT friend QDebug operator<<(QDebug d, QAnyStringView s); #endif [[nodiscard]] constexpr Tag tag() const noexcept { return Tag{m_size & TypeMask}; } [[nodiscard]] constexpr bool isUtf16() const noexcept { return tag() == Tag::Utf16; } [[nodiscard]] constexpr bool isUtf8() const noexcept { return tag() == Tag::Utf8; } [[nodiscard]] constexpr bool isLatin1() const noexcept { return tag() == Tag::Latin1; } [[nodiscard]] constexpr QStringView asStringView() const { return Q_ASSERT(isUtf16()), QStringView{m_data_utf16, size()}; } [[nodiscard]] constexpr q_no_char8_t::QUtf8StringView asUtf8StringView() const { return Q_ASSERT(isUtf8()), q_no_char8_t::QUtf8StringView{m_data_utf8, size()}; } [[nodiscard]] inline constexpr QLatin1StringView asLatin1StringView() const; [[nodiscard]] constexpr size_t charSize() const noexcept { return isUtf16() ? 2 : 1; } constexpr void setSize(qsizetype sz) noexcept { m_size = size_t(sz) | tag(); } constexpr void advanceData(qsizetype delta) noexcept { m_data_utf8 += delta * charSize(); } Q_ALWAYS_INLINE constexpr void verify([[maybe_unused]] qsizetype pos = 0, [[maybe_unused]] qsizetype n = 1) const { Q_ASSERT(pos >= 0); Q_ASSERT(pos <= size()); Q_ASSERT(n >= 0); Q_ASSERT(n <= size() - pos); } union { const void *m_data; const char *m_data_utf8; const char16_t *m_data_utf16; }; size_t m_size; friend class ::tst_QAnyStringView; }; Q_DECLARE_TYPEINFO(QAnyStringView, Q_PRIMITIVE_TYPE); template , std::is_same >, bool> = true> [[nodiscard]] inline QAnyStringView qToAnyStringViewIgnoringNull(const QStringLike &s) noexcept { return QAnyStringView(s.begin(), s.size()); } QT_END_NAMESPACE #endif /* QANYSTRINGVIEW_H */