diff options
author | Marc Mutz <marc.mutz@kdab.com> | 2020-05-20 15:20:07 +0200 |
---|---|---|
committer | Lars Knoll <lars.knoll@qt.io> | 2020-08-31 22:57:49 +0200 |
commit | 2c9529e158fc589c48e6b1fb61dca2133e33ac4d (patch) | |
tree | e9429715ab339d95c2ba9456bf39a894e8b41539 /src/corelib/text/qanystringview.h | |
parent | f0ae973244026ca5382f05630bd799b44154d224 (diff) |
Long live Q{Any,Utf8}StringView!
We need to add these two classes at the same time, because
QAnyStringView makes all QUtf8StringView relational operators moot. We
might want to add some later, esp. for UTF-8/UTf-8 comparisons, to
avoid the pessimization that we can't early-out on size() mismatch in
QAnyStringView equality operators, but that's an optimization, not a
correctness issue, and can be fixed in a source-compatible way even
after Qt 6 is released.
To deal with the char8_t problem in C++20, make QUtf8StringView a
class template out of which two UTF-8 views can be instantiated: the
Qt 7 version, which depends on C++20 char8_t as value_type, and the Qt
6 version where value_type is a char. Use inline namespaces to map the
QUtf8StringView identifier to one or the other, depending on the C++
version used to compile the user code. The inline namespace names must
needs be a bit ugly, as their inline'ness depends on __cpp_char8_t. If
we simply used q_v1/q_v2 we'd be blocking these names for Qt inline
namespaces forever, because it's likely that inline'ness of other
users of inline namespaces in Qt depends on things other than
__cpp_char8_t. While inline'ness of namespaces is, theoretically
speaking, a compile-time-only property, at least Clang warns about
mixed use of inline on a given namespace, so we need to bite the
bullet here. This is also the reason for the QT_BEGIN_..._NAMESPACE
macros: GCC is ok with the first declaration making a namespace
inline, while Clang warns upon re-opening an inline namespace as a
non-inline one.
[ChangeLog][QtCore][QUtf8StringView] New class.
[ChangeLog][QtCore][QAnyStringView] New class.
Change-Id: Ia7179760fca0e0b67d52f5accb0a62e389b17913
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/corelib/text/qanystringview.h')
-rw-r--r-- | src/corelib/text/qanystringview.h | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/src/corelib/text/qanystringview.h b/src/corelib/text/qanystringview.h new file mode 100644 index 0000000000..61761c6a82 --- /dev/null +++ b/src/corelib/text/qanystringview.h @@ -0,0 +1,292 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz <marc.mutz@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QANYSTRINGVIEW_H +#define QANYSTRINGVIEW_H + +#include <QtCore/qstringview.h> +#include <QtCore/qutf8stringview.h> + +QT_BEGIN_NAMESPACE + +template <typename, typename> class QStringBuilder; + +class QAnyStringView +{ +public: + typedef qptrdiff difference_type; + typedef qsizetype size_type; +private: + template <typename Char> + using if_compatible_char = std::enable_if_t<std::disjunction_v< + QtPrivate::IsCompatibleCharType<Char>, + QtPrivate::IsCompatibleChar8Type<Char> + >, bool>; + + template <typename Pointer> + using if_compatible_pointer = std::enable_if_t<std::disjunction_v< + QtPrivate::IsCompatiblePointer<Pointer>, + QtPrivate::IsCompatiblePointer8<Pointer> + >, bool>; + + + template <typename T> + using if_compatible_container = std::enable_if_t<std::disjunction_v< + QtPrivate::IsContainerCompatibleWithQStringView<T>, + QtPrivate::IsContainerCompatibleWithQUtf8StringView<T> + >, bool>; + + // confirm we don't make an accidental copy constructor: + static_assert(QtPrivate::IsContainerCompatibleWithQStringView<QAnyStringView>::value == false); + static_assert(QtPrivate::IsContainerCompatibleWithQUtf8StringView<QAnyStringView>::value == false); + + template <typename Char> + static constexpr std::size_t encodeType(qsizetype sz) noexcept + { + // only deals with Utf8 and Utf16 - there's only one way to create + // a Latin1 string, and that ctor deals with the tag itself + Q_ASSERT(sz >= 0); + Q_ASSERT(sz <= qsizetype(SizeMask)); + return std::size_t(sz) | uint(sizeof(Char) == sizeof(char16_t)) * Tag::Utf16; + } + + template <typename Char> + static qsizetype lengthHelperPointer(const Char *str) noexcept + { +#if defined(Q_CC_GNU) && !defined(Q_CC_CLANG) && !defined(Q_CC_INTEL) + if (__builtin_constant_p(*str)) { + qsizetype result = 0; + while (*str++ != u'\0') + ++result; + return result; + } +#endif + if constexpr (sizeof(Char) == sizeof(char16_t)) + return QtPrivate::qustrlen(reinterpret_cast<const char16_t*>(str)); + else + return qsizetype(strlen(reinterpret_cast<const char*>(str))); + } + + template <typename Container> + static constexpr qsizetype lengthHelperContainer(const Container &c) noexcept + { + return qsizetype(std::size(c)); + } + + template <typename Char, size_t N> + static constexpr qsizetype lengthHelperContainer(const Char (&)[N]) noexcept + { + return qsizetype(N - 1); + } + + 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 <typename Char, if_compatible_char<Char> = true> + constexpr QAnyStringView(const Char *str, qsizetype len) + : m_data{str}, + m_size{encodeType<Char>((Q_ASSERT(len >= 0), Q_ASSERT(str || !len), len))} {} + + template <typename Char, if_compatible_char<Char> = true> + constexpr QAnyStringView(const Char *f, const Char *l) + : QAnyStringView(f, l - f) {} + +#ifdef Q_CLANG_QDOC + template <typename Char, size_t N> + constexpr QAnyStringView(const Char (&array)[N]) noexcept; + + template <typename Char> + constexpr QAnyStringView(const Char *str) noexcept; +#else + + template <typename Pointer, if_compatible_pointer<Pointer> = 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(QLatin1String str) noexcept; + + // defined in qstringbuilder.h + template <typename A, typename B> + inline QAnyStringView(const QStringBuilder<A, B> &expr, + typename QStringBuilder<A, B>::ConvertTo &&capacity = {}); + + template <typename Container, if_compatible_container<Container> = true> + constexpr QAnyStringView(const Container &c) noexcept + : QAnyStringView(std::data(c), lengthHelperContainer(c)) {} + + template <typename Char, if_compatible_char<Char> = true> + constexpr QAnyStringView(const Char &c) noexcept + : QAnyStringView{&c, 1} {} + constexpr QAnyStringView(const QChar &c) noexcept + : QAnyStringView{&c, 1} {} + + template <typename Char, typename Container = decltype(QChar::fromUcs4(U'x')), + std::enable_if_t<std::is_same_v<Char, char32_t>, bool> = true> + constexpr QAnyStringView(Char c, Container &&capacity = {}) + : QAnyStringView(capacity = QChar::fromUcs4(c)) {} + + constexpr QAnyStringView(QStringView v) noexcept + : QAnyStringView(std::data(v), lengthHelperContainer(v)) {} + + template <bool UseChar8T> + constexpr QAnyStringView(QBasicUtf8StringView<UseChar8T> v) noexcept + : QAnyStringView(std::data(v), lengthHelperContainer(v)) {} + + // defined in qstring.h: + template <typename Visitor> + inline constexpr decltype(auto) visit(Visitor &&v) const; + + [[nodiscard]] inline QString toString() const; // defined in qstring.h + + [[nodiscard]] constexpr qsizetype size() const noexcept { return qsizetype(m_size & 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; + + // + // 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(); } +#if QT_DEPRECATED_SINCE(6, 0) + [[nodiscard]] + Q_DECL_DEPRECATED_X("Use size() and port callers to qsizetype.") + constexpr int length() const /* not nothrow! */ + { return Q_ASSERT(int(size()) == size()), int(size()); } +#endif +private: + // TODO: Optimize by inverting and storing the flags in the low bits and + // the size in the high. + static_assert(std::is_same_v<std::size_t, size_t>); + static_assert(sizeof(size_t) == sizeof(qsizetype)); + static constexpr size_t SizeMask = (std::numeric_limits<size_t>::max)() / 4; + static constexpr size_t Latin1Flag = SizeMask + 1; + static constexpr size_t TwoByteCodePointFlag = Latin1Flag << 1; + static constexpr size_t TypeMask = (std::numeric_limits<size_t>::max)() & ~SizeMask; + static_assert(TypeMask == (Latin1Flag|TwoByteCodePointFlag)); + // HI HI LO LO ... + // 0 0 SZ SZ Utf8 + // 0 1 SZ SZ Latin1 + // 1 0 SZ SZ Utf16 + // 1 1 SZ SZ Unused + // ^ ^ latin1 + // | sizeof code-point == 2 + enum Tag : size_t { + Utf8 = 0, + Latin1 = Latin1Flag, + Utf16 = TwoByteCodePointFlag, + Unused = TypeMask, + }; + [[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 QLatin1String asLatin1StringView() const; + [[nodiscard]] constexpr size_t charSize() const noexcept { return isUtf16() ? 2 : 1; } + Q_ALWAYS_INLINE constexpr void verify(qsizetype pos, qsizetype n = 0) 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; +}; +Q_DECLARE_TYPEINFO(QAnyStringView, Q_PRIMITIVE_TYPE); + +template <typename QStringLike, std::enable_if_t<std::disjunction_v< + std::is_same<QStringLike, QString>, + std::is_same<QStringLike, QByteArray> + >, bool> = true> +[[nodiscard]] inline QAnyStringView qToAnyStringViewIgnoringNull(const QStringLike &s) noexcept +{ return QAnyStringView(s.data(), s.size()); } + + +#define Q_ANY_SV_MAKE_RELOP(op) \ + [[nodiscard]] Q_ALWAYS_INLINE auto operator op (QAnyStringView lhs, QAnyStringView rhs) noexcept \ + { return QAnyStringView::compare(lhs, rhs) op 0; } \ + /* end */ + +Q_ANY_SV_MAKE_RELOP(==) // size() shortcut doesn't apply for UTF-8 vs. {L1, UTF-16} +Q_ANY_SV_MAKE_RELOP(!=) + +#ifdef __cpp_impl_three_way_comparison +Q_ANY_SV_MAKE_RELOP(<=>) +#else +Q_ANY_SV_MAKE_RELOP(<=) +Q_ANY_SV_MAKE_RELOP(>=) +Q_ANY_SV_MAKE_RELOP(<) +Q_ANY_SV_MAKE_RELOP(>) +#endif + +#undef Q_ANY_SV_MAKE_RELOP + +QT_END_NAMESPACE + +#endif /* QANYSTRINGVIEW_H */ |