1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz <marc.mutz@kdab.com> |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | #ifndef QANYSTRINGVIEW_H |
5 | #define QANYSTRINGVIEW_H |
6 | |
7 | #include <QtCore/qcompare.h> |
8 | #include <QtCore/qcontainerfwd.h> |
9 | #include <QtCore/qlatin1stringview.h> |
10 | #include <QtCore/qstringview.h> |
11 | #include <QtCore/qutf8stringview.h> |
12 | |
13 | #include <QtCore/q20type_traits.h> |
14 | #include <limits> |
15 | |
16 | class tst_QAnyStringView; |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | namespace QtPrivate { |
21 | |
22 | template <typename Tag, typename Result> |
23 | struct wrapped { using type = Result; }; |
24 | |
25 | template <typename Tag, typename Result> |
26 | using wrapped_t = typename wrapped<Tag, Result>::type; |
27 | |
28 | } // namespace QtPrivate |
29 | |
30 | class QAnyStringView |
31 | { |
32 | public: |
33 | typedef qptrdiff difference_type; |
34 | typedef qsizetype size_type; |
35 | private: |
36 | static constexpr size_t SizeMask = (std::numeric_limits<size_t>::max)() / 4; |
37 | #if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) || defined(QT_BOOTSTRAPPED) |
38 | static constexpr int SizeShift = 2; |
39 | static constexpr size_t Latin1Flag = 1; |
40 | #else |
41 | static constexpr int SizeShift = 0; |
42 | static constexpr size_t Latin1Flag = SizeMask + 1; |
43 | #endif |
44 | static constexpr size_t TwoByteCodePointFlag = Latin1Flag << 1; |
45 | static constexpr size_t TypeMask = ~(SizeMask << SizeShift); |
46 | static_assert(TypeMask == (Latin1Flag|TwoByteCodePointFlag)); |
47 | |
48 | // Tag bits |
49 | // 0 0 Utf8 |
50 | // 0 1 Latin1 |
51 | // 1 0 Utf16 |
52 | // 1 1 Unused |
53 | // ^ ^ latin1 |
54 | // | sizeof code-point == 2 |
55 | enum Tag : size_t { |
56 | Utf8 = 0, |
57 | Latin1 = Latin1Flag, |
58 | Utf16 = TwoByteCodePointFlag, |
59 | Unused = TypeMask, |
60 | }; |
61 | |
62 | template <typename Char> |
63 | using is_compatible_char = std::disjunction< |
64 | QtPrivate::IsCompatibleCharType<Char>, |
65 | QtPrivate::IsCompatibleChar8Type<Char> |
66 | >; |
67 | |
68 | template <typename Char> |
69 | using if_compatible_char = std::enable_if_t<is_compatible_char<Char>::value, bool>; |
70 | |
71 | template <typename Pointer> |
72 | using if_compatible_pointer = std::enable_if_t<std::disjunction_v< |
73 | QtPrivate::IsCompatiblePointer<Pointer>, |
74 | QtPrivate::IsCompatiblePointer8<Pointer> |
75 | >, bool>; |
76 | |
77 | |
78 | template <typename T> |
79 | using if_compatible_container = std::enable_if_t<std::disjunction_v< |
80 | QtPrivate::IsContainerCompatibleWithQStringView<T>, |
81 | QtPrivate::IsContainerCompatibleWithQUtf8StringView<T> |
82 | >, bool>; |
83 | |
84 | template <typename QStringOrQByteArray, typename T> |
85 | using if_convertible_to = std::enable_if_t<std::conjunction_v< |
86 | // need to exclude a bunch of stuff, because we take by universal reference: |
87 | std::negation<std::disjunction< |
88 | std::is_same<q20::remove_cvref_t<T>, QAnyStringView::Tag>, |
89 | std::is_same<q20::remove_cvref_t<T>, QAnyStringView>, // don't make a copy/move ctor |
90 | std::is_pointer<std::decay_t<T>>, // const char*, etc |
91 | is_compatible_char<T>, // don't create a QString/QByteArray, we have a ctor |
92 | std::is_same<q20::remove_cvref_t<T>, QByteArray>, |
93 | std::is_same<q20::remove_cvref_t<T>, QString> |
94 | >>, |
95 | // this is what we're really after: |
96 | std::is_convertible<T, QStringOrQByteArray> |
97 | >, bool>; |
98 | |
99 | // confirm we don't make an accidental copy constructor: |
100 | static_assert(QtPrivate::IsContainerCompatibleWithQStringView<QAnyStringView>::value == false); |
101 | static_assert(QtPrivate::IsContainerCompatibleWithQUtf8StringView<QAnyStringView>::value == false); |
102 | |
103 | template<typename Char> |
104 | static constexpr bool isAsciiOnlyCharsAtCompileTime(Char *str, qsizetype sz) noexcept |
105 | { |
106 | // do not perform check if not at compile time |
107 | if (!q20::is_constant_evaluated()) |
108 | return false; |
109 | if constexpr (sizeof(Char) != sizeof(char)) { |
110 | Q_UNUSED(str); |
111 | Q_UNUSED(sz); |
112 | return false; |
113 | } else { |
114 | for (qsizetype i = 0; i < sz; ++i) { |
115 | if (uchar(str[i]) > 0x7f) |
116 | return false; |
117 | } |
118 | return true; |
119 | } |
120 | } |
121 | |
122 | template<typename Char> |
123 | static constexpr std::size_t encodeType(const Char *str, qsizetype sz) noexcept |
124 | { |
125 | // Utf16 if 16 bit, Latin1 if ASCII, else Utf8 |
126 | Q_ASSERT(sz >= 0); |
127 | Q_ASSERT(sz <= qsizetype(SizeMask)); |
128 | Q_ASSERT(str || !sz); |
129 | return (std::size_t(sz) << SizeShift) |
130 | | uint(sizeof(Char) == sizeof(char16_t)) * Tag::Utf16 |
131 | | uint(isAsciiOnlyCharsAtCompileTime(str, sz)) * Tag::Latin1; |
132 | } |
133 | |
134 | template <typename Char> |
135 | static constexpr qsizetype lengthHelperPointer(const Char *str) noexcept |
136 | { |
137 | if (q20::is_constant_evaluated()) |
138 | return QtPrivate::lengthHelperPointer(str); |
139 | if constexpr (sizeof(Char) == sizeof(char16_t)) |
140 | return QtPrivate::qustrlen(str: reinterpret_cast<const char16_t*>(str)); |
141 | else |
142 | return qsizetype(strlen(s: reinterpret_cast<const char*>(str))); |
143 | } |
144 | |
145 | static QChar toQChar(char ch) noexcept { return toQChar(ch: QLatin1Char{ch}); } // we don't handle UTF-8 multibytes |
146 | static QChar toQChar(QChar ch) noexcept { return ch; } |
147 | static QChar toQChar(QLatin1Char ch) noexcept { return ch; } |
148 | |
149 | struct QCharContainer { // private, so users can't pass their own |
150 | explicit QCharContainer() = default; |
151 | QChar ch; |
152 | }; |
153 | |
154 | explicit constexpr QAnyStringView(const void *d, qsizetype n, std::size_t sizeAndType) noexcept |
155 | : m_data{d}, m_size{std::size_t(n) | (sizeAndType & TypeMask)} {} |
156 | public: |
157 | constexpr QAnyStringView() noexcept |
158 | : m_data{nullptr}, m_size{0} {} |
159 | constexpr QAnyStringView(std::nullptr_t) noexcept |
160 | : QAnyStringView() {} |
161 | |
162 | template <typename Char, if_compatible_char<Char> = true> |
163 | constexpr QAnyStringView(const Char *str, qsizetype len) |
164 | : m_data{str}, m_size{encodeType<Char>(str, len)} |
165 | { |
166 | } |
167 | |
168 | template <typename Char, if_compatible_char<Char> = true> |
169 | constexpr QAnyStringView(const Char *f, const Char *l) |
170 | : QAnyStringView(f, l - f) {} |
171 | |
172 | #ifdef Q_QDOC |
173 | template <typename Char, size_t N> |
174 | constexpr QAnyStringView(const Char (&array)[N]) noexcept; |
175 | |
176 | template <typename Char> |
177 | constexpr QAnyStringView(const Char *str) noexcept; |
178 | #else |
179 | |
180 | template <typename Pointer, if_compatible_pointer<Pointer> = true> |
181 | constexpr QAnyStringView(const Pointer &str) noexcept |
182 | : QAnyStringView{str, str ? lengthHelperPointer(str) : 0} {} |
183 | #endif |
184 | |
185 | // defined in qstring.h |
186 | inline QAnyStringView(const QByteArray &str) noexcept; // TODO: Should we have this at all? Remove? |
187 | inline QAnyStringView(const QString &str) noexcept; |
188 | inline constexpr QAnyStringView(QLatin1StringView str) noexcept; |
189 | |
190 | template <typename Container, if_compatible_container<Container> = true> |
191 | constexpr Q_ALWAYS_INLINE QAnyStringView(const Container &c) noexcept |
192 | : QAnyStringView(std::data(c), QtPrivate::lengthHelperContainer(c)) {} |
193 | |
194 | template <typename Container, if_convertible_to<QString, Container> = true> |
195 | constexpr QAnyStringView(Container &&c, QtPrivate::wrapped_t<Container, QString> &&capacity = {}) |
196 | //noexcept(std::is_nothrow_constructible_v<QString, Container>) |
197 | : QAnyStringView(capacity = std::forward<Container>(c)) {} |
198 | |
199 | template <typename Container, if_convertible_to<QByteArray, Container> = true> |
200 | constexpr QAnyStringView(Container &&c, QtPrivate::wrapped_t<Container, QByteArray> &&capacity = {}) |
201 | //noexcept(std::is_nothrow_constructible_v<QByteArray, Container>) |
202 | : QAnyStringView(capacity = std::forward<Container>(c)) {} |
203 | |
204 | template <typename Char, if_compatible_char<Char> = true> |
205 | constexpr QAnyStringView(const Char &c) noexcept |
206 | : QAnyStringView{&c, 1} {} |
207 | template <typename Char, if_convertible_to<QChar, Char> = true> |
208 | constexpr QAnyStringView(Char ch, QCharContainer &&capacity = QCharContainer()) noexcept |
209 | : QAnyStringView{&(capacity.ch = ch), 1} {} |
210 | template <typename Char, typename Container = decltype(QChar::fromUcs4(c: U'x')), |
211 | std::enable_if_t<std::is_same_v<Char, char32_t>, bool> = true> |
212 | constexpr QAnyStringView(Char c, Container &&capacity = {}) noexcept |
213 | : QAnyStringView(capacity = QChar::fromUcs4(c)) {} |
214 | |
215 | constexpr QAnyStringView(QStringView v) noexcept |
216 | : QAnyStringView(std::data(cont&: v), QtPrivate::lengthHelperContainer(c: v)) {} |
217 | |
218 | template <bool UseChar8T> |
219 | constexpr QAnyStringView(QBasicUtf8StringView<UseChar8T> v) noexcept |
220 | : QAnyStringView(std::data(v), QtPrivate::lengthHelperContainer(v)) {} |
221 | |
222 | template <typename Char, size_t Size, if_compatible_char<Char> = true> |
223 | [[nodiscard]] constexpr static QAnyStringView fromArray(const Char (&string)[Size]) noexcept |
224 | { return QAnyStringView(string, Size); } |
225 | |
226 | // defined in qstring.h: |
227 | template <typename Visitor> |
228 | inline constexpr decltype(auto) visit(Visitor &&v) const; |
229 | |
230 | [[nodiscard]] |
231 | constexpr QAnyStringView mid(qsizetype pos, qsizetype n = -1) const |
232 | { |
233 | using namespace QtPrivate; |
234 | auto result = QContainerImplHelper::mid(originalLength: size(), position: &pos, length: &n); |
235 | return result == QContainerImplHelper::Null ? QAnyStringView() : sliced(pos, n); |
236 | } |
237 | [[nodiscard]] |
238 | constexpr QAnyStringView left(qsizetype n) const |
239 | { |
240 | if (size_t(n) >= size_t(size())) |
241 | n = size(); |
242 | return sliced(pos: 0, n); |
243 | } |
244 | [[nodiscard]] |
245 | constexpr QAnyStringView right(qsizetype n) const |
246 | { |
247 | if (size_t(n) >= size_t(size())) |
248 | n = size(); |
249 | return sliced(pos: size() - n, n); |
250 | } |
251 | |
252 | [[nodiscard]] constexpr QAnyStringView sliced(qsizetype pos) const |
253 | { verify(pos, n: 0); auto r = *this; r.advanceData(delta: pos); r.setSize(size() - pos); return r; } |
254 | [[nodiscard]] constexpr QAnyStringView sliced(qsizetype pos, qsizetype n) const |
255 | { verify(pos, n); auto r = *this; r.advanceData(delta: pos); r.setSize(n); return r; } |
256 | [[nodiscard]] constexpr QAnyStringView first(qsizetype n) const |
257 | { verify(pos: 0, n); return sliced(pos: 0, n); } |
258 | [[nodiscard]] constexpr QAnyStringView last(qsizetype n) const |
259 | { verify(pos: 0, n); return sliced(pos: size() - n, n); } |
260 | [[nodiscard]] constexpr QAnyStringView chopped(qsizetype n) const |
261 | { verify(pos: 0, n); return sliced(pos: 0, n: size() - n); } |
262 | |
263 | constexpr QAnyStringView &slice(qsizetype pos) |
264 | { *this = sliced(pos); return *this; } |
265 | constexpr QAnyStringView &slice(qsizetype pos, qsizetype n) |
266 | { *this = sliced(pos, n); return *this; } |
267 | |
268 | constexpr void truncate(qsizetype n) |
269 | { verify(pos: 0, n); setSize(n); } |
270 | constexpr void chop(qsizetype n) |
271 | { verify(pos: 0, n); setSize(size() - n); } |
272 | |
273 | |
274 | [[nodiscard]] inline QString toString() const; // defined in qstring.h |
275 | |
276 | [[nodiscard]] constexpr qsizetype size() const noexcept |
277 | { return qsizetype((m_size >> SizeShift) & SizeMask); } |
278 | [[nodiscard]] constexpr const void *data() const noexcept { return m_data; } |
279 | |
280 | [[nodiscard]] Q_CORE_EXPORT static int compare(QAnyStringView lhs, QAnyStringView rhs, Qt::CaseSensitivity cs = Qt::CaseSensitive) noexcept; |
281 | [[nodiscard]] Q_CORE_EXPORT static bool equal(QAnyStringView lhs, QAnyStringView rhs) noexcept; |
282 | |
283 | static constexpr inline bool detects_US_ASCII_at_compile_time = |
284 | #ifdef QT_SUPPORTS_IS_CONSTANT_EVALUATED |
285 | true |
286 | #else |
287 | false |
288 | #endif |
289 | ; |
290 | |
291 | // |
292 | // STL compatibility API: |
293 | // |
294 | [[nodiscard]] constexpr QChar front() const; // NOT noexcept! |
295 | [[nodiscard]] constexpr QChar back() const; // NOT noexcept! |
296 | [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } |
297 | [[nodiscard]] constexpr qsizetype size_bytes() const noexcept |
298 | { return size() * charSize(); } |
299 | |
300 | [[nodiscard]] constexpr qsizetype max_size() const noexcept |
301 | { |
302 | // -1 to deal with the pointer one-past-the-end; |
303 | return QtPrivate::MaxAllocSize / charSize() - 1; |
304 | } |
305 | |
306 | // |
307 | // Qt compatibility API: |
308 | // |
309 | [[nodiscard]] constexpr bool isNull() const noexcept { return !m_data; } |
310 | [[nodiscard]] constexpr bool isEmpty() const noexcept { return empty(); } |
311 | [[nodiscard]] constexpr qsizetype length() const noexcept |
312 | { return size(); } |
313 | |
314 | private: |
315 | friend bool comparesEqual(const QAnyStringView &lhs, const QAnyStringView &rhs) noexcept |
316 | { return QAnyStringView::equal(lhs, rhs); } |
317 | friend Qt::strong_ordering |
318 | compareThreeWay(const QAnyStringView &lhs, const QAnyStringView &rhs) noexcept |
319 | { |
320 | const int res = QAnyStringView::compare(lhs, rhs); |
321 | return Qt::compareThreeWay(lhs: res, rhs: 0); |
322 | } |
323 | Q_DECLARE_STRONGLY_ORDERED(QAnyStringView) |
324 | |
325 | #ifndef QT_NO_DEBUG_STREAM |
326 | Q_CORE_EXPORT friend QDebug operator<<(QDebug d, QAnyStringView s); |
327 | #endif |
328 | |
329 | [[nodiscard]] constexpr Tag tag() const noexcept { return Tag{m_size & TypeMask}; } |
330 | [[nodiscard]] constexpr bool isUtf16() const noexcept { return tag() == Tag::Utf16; } |
331 | [[nodiscard]] constexpr bool isUtf8() const noexcept { return tag() == Tag::Utf8; } |
332 | [[nodiscard]] constexpr bool isLatin1() const noexcept { return tag() == Tag::Latin1; } |
333 | [[nodiscard]] constexpr QStringView asStringView() const |
334 | { return Q_ASSERT(isUtf16()), QStringView{m_data_utf16, size()}; } |
335 | [[nodiscard]] constexpr q_no_char8_t::QUtf8StringView asUtf8StringView() const |
336 | { return Q_ASSERT(isUtf8()), q_no_char8_t::QUtf8StringView{m_data_utf8, size()}; } |
337 | [[nodiscard]] inline constexpr QLatin1StringView asLatin1StringView() const; |
338 | [[nodiscard]] constexpr size_t charSize() const noexcept { return isUtf16() ? 2 : 1; } |
339 | constexpr void setSize(qsizetype sz) noexcept { m_size = size_t(sz) | tag(); } |
340 | constexpr void advanceData(qsizetype delta) noexcept |
341 | { m_data_utf8 += delta * charSize(); } |
342 | Q_ALWAYS_INLINE constexpr void verify([[maybe_unused]] qsizetype pos = 0, |
343 | [[maybe_unused]] qsizetype n = 1) const |
344 | { |
345 | Q_ASSERT(pos >= 0); |
346 | Q_ASSERT(pos <= size()); |
347 | Q_ASSERT(n >= 0); |
348 | Q_ASSERT(n <= size() - pos); |
349 | } |
350 | union { |
351 | const void *m_data; |
352 | const char *m_data_utf8; |
353 | const char16_t *m_data_utf16; |
354 | }; |
355 | size_t m_size; |
356 | friend class ::tst_QAnyStringView; |
357 | }; |
358 | Q_DECLARE_TYPEINFO(QAnyStringView, Q_PRIMITIVE_TYPE); |
359 | |
360 | template <typename QStringLike, std::enable_if_t<std::disjunction_v< |
361 | std::is_same<QStringLike, QString>, |
362 | std::is_same<QStringLike, QByteArray> |
363 | >, bool> = true> |
364 | [[nodiscard]] inline QAnyStringView qToAnyStringViewIgnoringNull(const QStringLike &s) noexcept |
365 | { return QAnyStringView(s.begin(), s.size()); } |
366 | |
367 | QT_END_NAMESPACE |
368 | |
369 | #endif /* QANYSTRINGVIEW_H */ |
370 | |