From ffac33964d51971a1c1f61308b6f0a60a4133296 Mon Sep 17 00:00:00 2001 From: Ivan Solovev Date: Fri, 10 May 2024 16:02:11 +0200 Subject: [PATCH] Add std::format support for qfloat16 The qfloat16 formatter is based of the std::formatter. It is added in a separate header file qfloat16format.h, so that the users could include it only when they really need. After the C++20 standard was released, it had one important addition that requires the format string to be parsed at compile-time. This feature is covered by __cpp_lib_format == 202106L. The older implementations have slightly different signatures of std::formatter::format() and std::formatter::parse() methods. To avoid ugly macros, Qt only supports std::format if the compilers implement __cpp_lib_format >= 202106L. This commit also defines QT_SUPPORTS_STD_FORMAT macro to avoid constant repetition of if (defined(__cpp_lib_format) && (__cpp_lib_format >= 202106L)) checks. [ChangeLog][QtCore][qfloat16] Added std::format support for qfloat16. The supported formatting options are similar to those of std::formatter for float. Fixes: QTBUG-104654 Change-Id: I3a0b077a55fbb46573de8b7bcd288b4ef9541953 Reviewed-by: Thiago Macieira --- src/corelib/CMakeLists.txt | 2 + src/corelib/io/qfloat16format.h | 31 ++++ src/corelib/io/qtformat_impl.h | 25 +++ tests/auto/corelib/io/CMakeLists.txt | 6 + .../corelib/io/qfloat16format/CMakeLists.txt | 21 +++ .../io/qfloat16format/tst_qfloat16format.cpp | 168 ++++++++++++++++++ 6 files changed, 253 insertions(+) create mode 100644 src/corelib/io/qfloat16format.h create mode 100644 src/corelib/io/qtformat_impl.h create mode 100644 tests/auto/corelib/io/qfloat16format/CMakeLists.txt create mode 100644 tests/auto/corelib/io/qfloat16format/tst_qfloat16format.cpp diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 1055cf4ab53..a2e07d7e354 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -111,6 +111,7 @@ qt_internal_add_module(Core io/qfilesystementry.cpp io/qfilesystementry_p.h io/qfilesystemiterator_p.h io/qfilesystemmetadata_p.h + io/qfloat16format.h io/qfsfileengine.cpp io/qfsfileengine_p.h io/qfsfileengine_iterator.cpp io/qfsfileengine_iterator_p.h io/qiodevice.cpp io/qiodevice.h io/qiodevice_p.h @@ -127,6 +128,7 @@ qt_internal_add_module(Core io/qstorageinfo.cpp io/qstorageinfo.h io/qstorageinfo_p.h io/qtemporarydir.cpp io/qtemporarydir.h io/qtemporaryfile.cpp io/qtemporaryfile.h io/qtemporaryfile_p.h + io/qtformat_impl.h io/qurl.cpp io/qurl.h io/qurl_p.h io/qurlidna.cpp io/qurlquery.cpp io/qurlquery.h diff --git a/src/corelib/io/qfloat16format.h b/src/corelib/io/qfloat16format.h new file mode 100644 index 00000000000..5db7d449bd0 --- /dev/null +++ b/src/corelib/io/qfloat16format.h @@ -0,0 +1,31 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QFLOAT16FORMAT_H +#define QFLOAT16FORMAT_H + +#if 0 +#pragma qt_class(QFloat16Format) +#pragma qt_sync_skip_header_check +#endif + +#include +#include + +#ifdef QT_SUPPORTS_STD_FORMAT + +#include + +template +struct std::formatter : std::formatter +{ + template + auto format(QT_PREPEND_NAMESPACE(qfloat16) val, FormatContext &ctx) const + { + return std::formatter::format(float(val), ctx); + } +}; + +#endif // QT_SUPPORTS_STD_FORMAT + +#endif // QFLOAT16FORMAT_H diff --git a/src/corelib/io/qtformat_impl.h b/src/corelib/io/qtformat_impl.h new file mode 100644 index 00000000000..7c7029d7799 --- /dev/null +++ b/src/corelib/io/qtformat_impl.h @@ -0,0 +1,25 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QTFORMAT_IMPL_H +#define QTFORMAT_IMPL_H + +#if 0 +#pragma qt_no_master_include +#pragma qt_sync_skip_header_check +#endif + +#include +#include + +#if __has_include() +# include +#endif + +#if (defined(__cpp_lib_format) && (__cpp_lib_format >= 202106L)) + +#define QT_SUPPORTS_STD_FORMAT 1 + +#endif // __cpp_lib_format + +#endif // QTFORMAT_IMPL_H diff --git a/tests/auto/corelib/io/CMakeLists.txt b/tests/auto/corelib/io/CMakeLists.txt index 291dbfb413a..1e66aa8956b 100644 --- a/tests/auto/corelib/io/CMakeLists.txt +++ b/tests/auto/corelib/io/CMakeLists.txt @@ -68,3 +68,9 @@ endif() if(QT_FEATURE_private_tests) add_subdirectory(qzip) endif() +if(NOT (MACOS AND "$ENV{QT_BUILD_ENVIRONMENT}" STREQUAL "ci")) + # On macOS the new features require at least macOS 13.3, + # but we also run the tests on older OS versions. + # So just skip macOS on CI for now. + add_subdirectory(qfloat16format) +endif() diff --git a/tests/auto/corelib/io/qfloat16format/CMakeLists.txt b/tests/auto/corelib/io/qfloat16format/CMakeLists.txt new file mode 100644 index 00000000000..0980007f697 --- /dev/null +++ b/tests/auto/corelib/io/qfloat16format/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qfloat16format LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +set(CMAKE_OSX_DEPLOYMENT_TARGET 13.3) + +qt_internal_add_test(tst_qfloat16format + SOURCES + tst_qfloat16format.cpp +) + +set_target_properties(tst_qfloat16format + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED OFF +) diff --git a/tests/auto/corelib/io/qfloat16format/tst_qfloat16format.cpp b/tests/auto/corelib/io/qfloat16format/tst_qfloat16format.cpp new file mode 100644 index 00000000000..63a27d55f65 --- /dev/null +++ b/tests/auto/corelib/io/qfloat16format/tst_qfloat16format.cpp @@ -0,0 +1,168 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include + +#include +#include +#include +#include + +using namespace Qt::StringLiterals; + +class tst_QFloat16Format : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void formatCompileTime(); + void format_data(); + void format(); + void formatMultiArg(); +}; + +void tst_QFloat16Format::initTestCase() +{ +#ifndef QT_SUPPORTS_STD_FORMAT + QSKIP("This test requires std::format support!"); +#endif +} + +void tst_QFloat16Format::formatCompileTime() +{ +#ifdef QT_SUPPORTS_STD_FORMAT + // Starting from __cpp_lib_format == 202106L, + // std::format requires the format string to be evaluated at compile-time, + // so check it here. + + const qfloat16 val{1.234f}; + std::locale loc{"C"}; + + // char + std::string buffer; + std::format_to(std::back_inserter(buffer), "{}", val); + std::format_to(std::back_inserter(buffer), "{:*>15.7f}", val); + std::format_to(std::back_inserter(buffer), "{:*^+#15.7g}", val); + std::format_to(std::back_inserter(buffer), "{:*<-#15.7A}", val); + std::format_to(std::back_inserter(buffer), "{:*^ 15.7e}", val); + std::format_to(std::back_inserter(buffer), loc, "{:*^10.3Lf}", val); + std::format_to(std::back_inserter(buffer), loc, "{:*< 10.7LE}", val); + + // wchar_t + std::wstring wbuffer; + std::format_to(std::back_inserter(wbuffer), L"{}", val); + std::format_to(std::back_inserter(wbuffer), L"{:*>15.7f}", val); + std::format_to(std::back_inserter(wbuffer), L"{:*^+#15.7g}", val); + std::format_to(std::back_inserter(wbuffer), L"{:*<-#15.7A}", val); + std::format_to(std::back_inserter(wbuffer), L"{:*^ 15.7e}", val); + std::format_to(std::back_inserter(wbuffer), loc, L"{:*^10.3Lf}", val); + std::format_to(std::back_inserter(wbuffer), loc, L"{:*< 10.7LE}", val); +#endif // QT_SUPPORTS_STD_FORMAT +} + +void tst_QFloat16Format::format_data() +{ + QTest::addColumn("format"); + QTest::addColumn("value"); + QTest::addColumn("locName"); + QTest::addColumn("expectedString"); + + auto row = [](const QString &format, qfloat16 val, const QString &expected, + const QByteArray &loc = QByteArray()) + { + QString str; + QDebug dbg(&str); + dbg.nospace().noquote() << format; + if (!loc.isEmpty()) + dbg.nospace().noquote() << "_" << loc; + + QTest::newRow(qPrintable(str)) << format << val << loc << expected; + }; + + QByteArray loc; +#if defined(Q_CC_MSVC_ONLY) + loc = "de-DE"_ba; +#elif !defined(Q_CC_MINGW) // minGW has only C and POSIX locales + loc = "de_DE"_ba; +#endif + + row(u"{}"_s, qfloat16(1.f), u"1"_s); + row(u"{:#}"_s, qfloat16(1.f), u"1."_s); + row(u"{:f}"_s, qfloat16(1.f), u"1.000000"_s); + row(u"{:*>10.2a}"_s, qfloat16(-1.23f), u"**-1.3bp+0"_s); + if (!loc.isEmpty()) { + row(u"{:+Lf}"_s, qfloat16(1.f), u"+1,000000"_s, loc); + row(u"{:*^10.3LF}"_s, qfloat16(-0.1234f), u"**-0,123**"_s, loc); + row(u"{:*^#10.4Lg}"_s, qfloat16(-1.f), u"**-1,000**"_s, loc); + row(u"{:*<14.3LE}"_s, qfloat16(-0.1234f), u"-1,234E-01****"_s, loc); + } +} + +void tst_QFloat16Format::format() +{ +#ifdef QT_SUPPORTS_STD_FORMAT + QFETCH(const QString, format); + QFETCH(const qfloat16, value); + QFETCH(const QByteArray, locName); + QFETCH(const QString, expectedString); + + // char + { + std::string buffer; + const auto formatStr = format.toStdString(); + if (!locName.isEmpty()) { + std::locale loc(locName.constData()); + std::vformat_to(std::back_inserter(buffer), loc, formatStr, + std::make_format_args(value)); + } else { + std::vformat_to(std::back_inserter(buffer), formatStr, + std::make_format_args(value)); + } + const QString actualString = QString::fromStdString(buffer); + QCOMPARE_EQ(actualString, expectedString); + } + + // wchar_t + { + std::wstring buffer; + const auto formatStr = format.toStdWString(); + if (!locName.isEmpty()) { + std::locale loc(locName.constData()); + std::vformat_to(std::back_inserter(buffer), loc, formatStr, + std::make_wformat_args(value)); + } else { + std::vformat_to(std::back_inserter(buffer), formatStr, + std::make_wformat_args(value)); + } + const QString actualString = QString::fromStdWString(buffer); + QCOMPARE_EQ(actualString, expectedString); + } +#endif // QT_SUPPORTS_STD_FORMAT +} + +void tst_QFloat16Format::formatMultiArg() +{ +#ifdef QT_SUPPORTS_STD_FORMAT + const qfloat16 v1{-0.1234f}; + const qfloat16 v2{5.67f}; + + const QString expectedString = u"**+5.67**_*****-1.234E-01"_s; + // char + { + std::string buffer; + std::format_to(std::back_inserter(buffer), "{1:*^+9.2f}_{0:*>15.3E}", v1, v2); + QCOMPARE_EQ(QString::fromStdString(buffer), expectedString); + } + + // wchar_t + { + std::wstring buffer; + std::format_to(std::back_inserter(buffer), L"{1:*^+9.2f}_{0:*>15.3E}", v1, v2); + QCOMPARE_EQ(QString::fromStdWString(buffer), expectedString); + } +#endif // QT_SUPPORTS_STD_FORMAT +} + +QTEST_MAIN(tst_QFloat16Format) +#include "tst_qfloat16format.moc"