Add std::format support for qfloat16

The qfloat16 formatter is based of the std::formatter<float>. 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 <thiago.macieira@intel.com>
This commit is contained in:
Ivan Solovev 2024-05-10 16:02:11 +02:00
parent 3c81fc5d73
commit ffac33964d
6 changed files with 253 additions and 0 deletions

View File

@ -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

View File

@ -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 <QtCore/qglobal.h>
#include <QtCore/qtformat_impl.h>
#ifdef QT_SUPPORTS_STD_FORMAT
#include <QtCore/qfloat16.h>
template <typename CharT>
struct std::formatter<QT_PREPEND_NAMESPACE(qfloat16), CharT> : std::formatter<float, CharT>
{
template <typename FormatContext>
auto format(QT_PREPEND_NAMESPACE(qfloat16) val, FormatContext &ctx) const
{
return std::formatter<float, CharT>::format(float(val), ctx);
}
};
#endif // QT_SUPPORTS_STD_FORMAT
#endif // QFLOAT16FORMAT_H

View File

@ -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 <QtCore/qsystemdetection.h>
#include <QtCore/qtconfigmacros.h>
#if __has_include(<format>)
# include <format>
#endif
#if (defined(__cpp_lib_format) && (__cpp_lib_format >= 202106L))
#define QT_SUPPORTS_STD_FORMAT 1
#endif // __cpp_lib_format
#endif // QTFORMAT_IMPL_H

View File

@ -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()

View File

@ -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
)

View File

@ -0,0 +1,168 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QtCore/qcompilerdetection.h>
#include <QtCore/qdebug.h>
#include <QtCore/qfloat16format.h>
#include <QtCore/qstring.h>
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<QString>("format");
QTest::addColumn<qfloat16>("value");
QTest::addColumn<QByteArray>("locName");
QTest::addColumn<QString>("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"