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