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
17class tst_QAnyStringView;
18
19QT_BEGIN_NAMESPACE
20
21namespace QtPrivate {
22
23template <typename Tag, typename Result>
24struct wrapped { using type = Result; };
25
26template <typename Tag, typename Result>
27using wrapped_t = typename wrapped<Tag, Result>::type;
28
29} // namespace QtPrivate
30
31class QAnyStringView
32{
33public:
34 typedef qptrdiff difference_type;
35 typedef qsizetype size_type;
36private:
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)} {}
179public:
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
325private:
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};
373Q_DECLARE_TYPEINFO(QAnyStringView, Q_PRIMITIVE_TYPE);
374
375template <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
382QT_END_NAMESPACE
383
384#endif /* QANYSTRINGVIEW_H */
385

source code of qtbase/src/corelib/text/qanystringview.h