From 25b0580ec788a39ffa06c98779faa8d1aeec9b80 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Tue, 23 Jul 2024 15:36:15 -0700 Subject: [PATCH] QOffsetStringArray: add support for UTF-8 and UTF-16 arrays std::byte not tested and not included in the concept requires {} clause, but would work otherwise. Fixes: QTBUG-100556 Change-Id: I4878533dcb2d4b3e8efefffd17e4f87d96a2d126 Reviewed-by: Ahmad Samir Reviewed-by: Thiago Macieira --- src/corelib/tools/qoffsetstringarray_p.h | 67 ++++++++++----- .../tools/qoffsetstringarray/CMakeLists.txt | 3 + .../tst_qoffsetstringarray.cpp | 82 +++++++++++++++++++ 3 files changed, 132 insertions(+), 20 deletions(-) diff --git a/src/corelib/tools/qoffsetstringarray_p.h b/src/corelib/tools/qoffsetstringarray_p.h index a4ef8a3e48f..e022024a53a 100644 --- a/src/corelib/tools/qoffsetstringarray_p.h +++ b/src/corelib/tools/qoffsetstringarray_p.h @@ -26,6 +26,10 @@ #include #include +#ifdef __cpp_concepts +# include +#endif + class tst_QOffsetStringArray; QT_BEGIN_NAMESPACE @@ -40,22 +44,39 @@ QT_WARNING_DISABLE_GCC("-Wstringop-overread") template class QOffsetStringArray { + static auto viewType_helper() + { + // returning std::type_identity here to avoid having to #include + if constexpr (sizeof(Char) == 2) { + return q20::type_identity(); +#ifdef __cpp_char8_t + } else if constexpr (std::is_same_v) { + return q20::type_identity(); +#endif + } else { + return q20::type_identity(); + } + } + public: + using Char = typename StaticString::value_type; + using View = typename decltype(viewType_helper())::type; + constexpr QOffsetStringArray(const StaticString &string, const OffsetList &offsets) : m_string(string), m_offsets(offsets) {} - constexpr const char *operator[](const int index) const noexcept + constexpr const Char *operator[](const int index) const noexcept { return m_string.data() + m_offsets[qBound(int(0), index, count())]; } - constexpr const char *at(const int index) const noexcept + constexpr const Char *at(const int index) const noexcept { return m_string.data() + m_offsets[index]; } - constexpr QByteArrayView viewAt(qsizetype index) const noexcept + constexpr View viewAt(qsizetype index) const noexcept { return { m_string.data() + m_offsets[index], qsizetype(m_offsets[index + 1]) - qsizetype(m_offsets[index]) - 1 }; @@ -63,7 +84,7 @@ public: constexpr int count() const { return int(m_offsets.size()) - 1; } - bool contains(QByteArrayView needle, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + bool contains(View needle, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept { for (qsizetype i = 0; i < count(); ++i) { if (viewAt(i).compare(needle, cs) == 0) @@ -93,15 +114,15 @@ template constexpr auto minifyValue() } } -template +template constexpr auto makeStaticString(Extractor extract, const T &... entries) { // append an extra null terminator - std::array result = {}; + std::array result = {}; qptrdiff offset = 0; - const char *strings[] = { extract(entries).operator const char *()... }; - size_t lengths[] = { sizeof(extract(T{}))... }; + const Char *strings[] = { extract(entries).operator const Char *()... }; + size_t lengths[] = { (sizeof(extract(T{})) / sizeof(Char))... }; for (size_t i = 0; i < std::size(strings); ++i) { q20::copy_n(strings[i], lengths[i], result.begin() + offset); offset += lengths[i]; @@ -109,40 +130,46 @@ constexpr auto makeStaticString(Extractor extract, const T &... entries) return result; } -template struct StaticString +template struct StaticString { - char value[N] = {}; + Char value[N] = {}; constexpr StaticString() = default; - constexpr StaticString(const char (&s)[N]) { q20::copy_n(s, N, value); } - constexpr operator const char *() const { return value; } + constexpr StaticString(const Char (&s)[N]) { q20::copy_n(s, N, value); } + constexpr operator const Char *() const { return value; } }; -template +template constexpr auto makeOffsetStringArray(StringExtractor extractString, const T &... entries) { constexpr size_t Count = sizeof...(T); - constexpr size_t StringLength = (sizeof(extractString(T{})) + ...); + constexpr size_t StringLength = (sizeof(extractString(T{})) + ...) / sizeof(Char); using MinifiedOffsetType = decltype(QtPrivate::minifyValue()); size_t offset = 0; - std::array fullOffsetList = { offset += sizeof(extractString(T{}))... }; + std::array fullOffsetList = { offset += (sizeof(extractString(T{})) / sizeof(Char))... }; - // prepend zero + // prepend the first offset (zero) pointing to the *start* of the first element std::array minifiedOffsetList = {}; q20::transform(fullOffsetList.begin(), fullOffsetList.end(), minifiedOffsetList.begin() + 1, [] (auto e) { return MinifiedOffsetType(e); }); - std::array staticString = QtPrivate::makeStaticString(extractString, entries...); + std::array staticString = QtPrivate::makeStaticString(extractString, entries...); return QOffsetStringArray(staticString, minifiedOffsetList); } } // namespace QtPrivate -template -constexpr auto qOffsetStringArray(const char (&...strings)[Nx]) noexcept +template +#ifdef __cpp_concepts +requires std::is_same_v || std::is_same_v +# ifdef __cpp_char8_t + || std::is_same_v +# endif +#endif +constexpr auto qOffsetStringArray(const Char (&...strings)[Nx]) noexcept { auto extractString = [](const auto &s) -> decltype(auto) { return s; }; - return QtPrivate::makeOffsetStringArray(extractString, QtPrivate::StaticString(strings)...); + return QtPrivate::makeOffsetStringArray(extractString, QtPrivate::StaticString(strings)...); } QT_WARNING_POP diff --git a/tests/auto/corelib/tools/qoffsetstringarray/CMakeLists.txt b/tests/auto/corelib/tools/qoffsetstringarray/CMakeLists.txt index d0205cfa155..cb3d5d69825 100644 --- a/tests/auto/corelib/tools/qoffsetstringarray/CMakeLists.txt +++ b/tests/auto/corelib/tools/qoffsetstringarray/CMakeLists.txt @@ -17,6 +17,9 @@ qt_internal_add_test(tst_qoffsetstringarray LIBRARIES Qt::CorePrivate ) +if (NOT VXWORKS) + set_property(TARGET tst_qoffsetstringarray PROPERTY CXX_STANDARD 20) +endif() if (CLANG) target_compile_options(tst_qoffsetstringarray diff --git a/tests/auto/corelib/tools/qoffsetstringarray/tst_qoffsetstringarray.cpp b/tests/auto/corelib/tools/qoffsetstringarray/tst_qoffsetstringarray.cpp index 3265a9d9979..736e9ae4342 100644 --- a/tests/auto/corelib/tools/qoffsetstringarray/tst_qoffsetstringarray.cpp +++ b/tests/auto/corelib/tools/qoffsetstringarray/tst_qoffsetstringarray.cpp @@ -25,6 +25,23 @@ constexpr const auto messages = qOffsetStringArray( "level - 4" ); +// When compiled with C++20, this is using the native char8_t +constexpr auto utf8Messages = qOffsetStringArray( + u8"level - 0", + u8"level - 1", + u8"level - 2", + u8"level - 3", + u8"level - 4" +); + +constexpr auto utf16Messages = qOffsetStringArray( + u"level - 0", + u"level - 1", + u"level - 2", + u"level - 3", + u"level - 4" +); + constexpr const auto messages257 = qOffsetStringArray( "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", @@ -69,6 +86,14 @@ void tst_QOffsetStringArray::init() static_assert(messages.m_offsets.size() == 6); static_assert(std::is_same_v); + static_assert(utf8Messages.m_string.size() == 51); + static_assert(utf8Messages.m_offsets.size() == 6); + static_assert(std::is_same_v); + + static_assert(utf16Messages.m_string.size() == 51); + static_assert(utf16Messages.m_offsets.size() == 6); + static_assert(std::is_same_v); + static_assert(messages257.m_offsets.size() == 258); static_assert(messages257.m_string.size() == 261); static_assert(std::is_same_v); @@ -88,6 +113,45 @@ void tst_QOffsetStringArray::access() // out of bounds returns empty strings: QCOMPARE(messages[5], ""); QCOMPARE(messages[6], ""); + + auto view0 = messages.viewAt(0); + static_assert(std::is_same_v); + QCOMPARE(view0, "level - 0"); + + QCOMPARE(utf8Messages[0], QUtf8StringView(u8"level - 0")); + QCOMPARE(utf8Messages[1], QUtf8StringView(u8"level - 1")); + QCOMPARE(utf8Messages[2], QUtf8StringView(u8"level - 2")); + QCOMPARE(utf8Messages[3], QUtf8StringView(u8"level - 3")); + QCOMPARE(utf8Messages[4], QUtf8StringView(u8"level - 4")); + QCOMPARE(utf8Messages[5], QUtf8StringView(u8"")); + QCOMPARE(utf8Messages[6], QUtf8StringView(u8"")); + + auto u8view0 = utf8Messages.viewAt(0); +#ifdef __cpp_char8_t + static_assert(std::is_same_v); +#endif + QCOMPARE(u8view0, u8"level - 0"); + QCOMPARE(utf8Messages.viewAt(1), u8"level - 1"); + QCOMPARE(utf8Messages.viewAt(2), u8"level - 2"); + QCOMPARE(utf8Messages.viewAt(3), u8"level - 3"); + QCOMPARE(utf8Messages.viewAt(4), u8"level - 4"); + // viewAt has no size checking! + + QCOMPARE(utf16Messages[0], QStringView(u"level - 0")); + QCOMPARE(utf16Messages[1], QStringView(u"level - 1")); + QCOMPARE(utf16Messages[2], QStringView(u"level - 2")); + QCOMPARE(utf16Messages[3], QStringView(u"level - 3")); + QCOMPARE(utf16Messages[4], QStringView(u"level - 4")); + QCOMPARE(utf16Messages[5], QStringView(u"")); + QCOMPARE(utf16Messages[6], QStringView(u"")); + + auto uview0 = utf16Messages.viewAt(0); + static_assert(std::is_same_v); + QCOMPARE(uview0, u"level - 0"); + QCOMPARE(utf16Messages.viewAt(1), u"level - 1"); + QCOMPARE(utf16Messages.viewAt(2), u"level - 2"); + QCOMPARE(utf16Messages.viewAt(3), u"level - 3"); + QCOMPARE(utf16Messages.viewAt(4), u"level - 4"); } void tst_QOffsetStringArray::contains() @@ -99,6 +163,24 @@ void tst_QOffsetStringArray::contains() QByteArray L4 = "Level - 4"; QVERIFY( messages.contains(L4, Qt::CaseInsensitive)); QVERIFY(!messages.contains(L4, Qt::CaseSensitive)); + + QVERIFY(!utf8Messages.contains(u8"")); + QVERIFY( utf8Messages.contains(u8"level - 0")); +#ifdef __cpp_lib_char8_t + std::u8string u8l2 = u8"level - 2"; // make sure we don't compare pointer values + QVERIFY( utf8Messages.contains(u8l2)); +#endif + QUtf8StringView u8l4 = u8"Level - 4"; + QVERIFY( utf8Messages.contains(u8l4, Qt::CaseInsensitive)); + QVERIFY(!utf8Messages.contains(u8l4, Qt::CaseSensitive)); + + QVERIFY(!utf16Messages.contains(u"")); + QVERIFY( utf16Messages.contains(u"level - 0")); + std::u16string ul2 = u"level - 2"; // make sure we don't compare pointer values + QVERIFY( utf16Messages.contains(ul2)); + QString ul4 = "Level - 4"; + QVERIFY( utf16Messages.contains(ul4, Qt::CaseInsensitive)); + QVERIFY(!utf16Messages.contains(ul4, Qt::CaseSensitive)); } QTEST_APPLESS_MAIN(tst_QOffsetStringArray)