From 69d60b5f1c9e5375f5aa49422aa5266ebcbbcf5d Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sat, 12 Aug 2023 20:53:03 +0800 Subject: [PATCH] Specify interface requirements as an C++ concept PR #19440. --- src/base/CMakeLists.txt | 3 +- .../istringable.h => concepts/stringable.h} | 12 +-- src/base/path.cpp | 4 + src/base/path.h | 6 +- src/base/settingsstorage.h | 20 ++--- src/base/utils/version.cpp | 34 +++++++++ src/base/utils/version.h | 11 ++- test/CMakeLists.txt | 1 + test/testconceptsstringable.cpp | 73 +++++++++++++++++++ test/testutilsversion.cpp | 8 +- 10 files changed, 144 insertions(+), 28 deletions(-) rename src/base/{interfaces/istringable.h => concepts/stringable.h} (86%) create mode 100644 src/base/utils/version.cpp create mode 100644 test/testconceptsstringable.cpp diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 42bca388a..78283c276 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -40,6 +40,7 @@ add_library(qbt_base STATIC bittorrent/torrentinfo.h bittorrent/tracker.h bittorrent/trackerentry.h + concepts/stringable.h digest32.h exceptions.h global.h @@ -54,7 +55,6 @@ add_library(qbt_base STATIC iconprovider.h indexrange.h interfaces/iapplication.h - interfaces/istringable.h logger.h net/dnsupdater.h net/downloadhandlerimpl.h @@ -187,6 +187,7 @@ add_library(qbt_base STATIC utils/random.cpp utils/string.cpp utils/thread.cpp + utils/version.cpp ) target_link_libraries(qbt_base diff --git a/src/base/interfaces/istringable.h b/src/base/concepts/stringable.h similarity index 86% rename from src/base/interfaces/istringable.h rename to src/base/concepts/stringable.h index 5cbc36a9e..221470fb6 100644 --- a/src/base/interfaces/istringable.h +++ b/src/base/concepts/stringable.h @@ -28,13 +28,13 @@ #pragma once +#include + class QString; -class IStringable +template +concept Stringable = requires (T t) { -public: - virtual ~IStringable() = default; - - // requirement: T(const QString &) constructor for derived class `T` // TODO: try enforce it in C++20 concept - virtual QString toString() const = 0; + requires std::constructible_from; + { t.toString() } -> std::same_as; }; diff --git a/src/base/path.cpp b/src/base/path.cpp index 2ed951773..14950d473 100644 --- a/src/base/path.cpp +++ b/src/base/path.cpp @@ -39,6 +39,7 @@ #include #include +#include "base/concepts/stringable.h" #include "base/global.h" #if defined(Q_OS_WIN) @@ -69,6 +70,9 @@ namespace #endif } +// `Path` should satisfy `Stringable` concept in order to be stored in settings as string +static_assert(Stringable); + Path::Path(const QString &pathStr) : m_pathStr {cleanPath(pathStr)} { diff --git a/src/base/path.h b/src/base/path.h index 17dcbb40a..8e222bc64 100644 --- a/src/base/path.h +++ b/src/base/path.h @@ -36,11 +36,9 @@ #include "pathfwd.h" -#include "base/interfaces/istringable.h" - class QStringView; -class Path final : public IStringable +class Path final { public: Path() = default; @@ -71,7 +69,7 @@ public: Path relativePathOf(const Path &childPath) const; QString data() const; - QString toString() const override; + QString toString() const; std::filesystem::path toStdFsPath() const; Path &operator/=(const Path &other); diff --git a/src/base/settingsstorage.h b/src/base/settingsstorage.h index 130339540..7abbdae46 100644 --- a/src/base/settingsstorage.h +++ b/src/base/settingsstorage.h @@ -38,7 +38,7 @@ #include #include -#include "base/interfaces/istringable.h" +#include "base/concepts/stringable.h" #include "utils/string.h" template @@ -46,7 +46,7 @@ concept IsQFlags = std::same_as>; // There are 2 ways for class `T` provide serialization support into `SettingsStorage`: // 1. If the `T` state is intended for users to edit (via a text editor), then -// implement `IStringable` interface +// `T` should satisfy `Stringable` concept // 2. Otherwise, use `Q_DECLARE_METATYPE(T)` and let `QMetaType` handle the serialization class SettingsStorage final : public QObject { @@ -64,7 +64,12 @@ public: template T loadValue(const QString &key, const T &defaultValue = {}) const { - if constexpr (std::is_base_of_v) + if constexpr (std::same_as) + { + // fast path for loading QVariant + return loadValueImpl(key, defaultValue); + } + else if constexpr (Stringable) { const QString value = loadValue(key, defaultValue.toString()); return T {value}; @@ -79,11 +84,6 @@ public: const typename T::Int value = loadValue(key, static_cast(defaultValue)); return T {value}; } - else if constexpr (std::is_same_v) - { - // fast path for loading QVariant - return loadValueImpl(key, defaultValue); - } else { const QVariant value = loadValueImpl(key); @@ -95,7 +95,9 @@ public: template void storeValue(const QString &key, const T &value) { - if constexpr (std::is_base_of_v) + if constexpr (std::same_as) + storeValueImpl(key, value); + else if constexpr (Stringable) storeValueImpl(key, value.toString()); else if constexpr (std::is_enum_v) storeValueImpl(key, Utils::String::fromEnum(value)); diff --git a/src/base/utils/version.cpp b/src/base/utils/version.cpp new file mode 100644 index 000000000..a8f24b624 --- /dev/null +++ b/src/base/utils/version.cpp @@ -0,0 +1,34 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Mike Tzou (Chocobo1) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "version.h" + +#include "base/concepts/stringable.h" + +// `Version` should satisfy `Stringable` concept in order to be stored in settings as string +static_assert(Stringable>); diff --git a/src/base/utils/version.h b/src/base/utils/version.h index 2d981bd83..36f20512e 100644 --- a/src/base/utils/version.h +++ b/src/base/utils/version.h @@ -36,14 +36,12 @@ #include #include -#include "base/interfaces/istringable.h" - namespace Utils { // This class provides a default implementation of `isValid()` that should work for most cases // It is ultimately up to the user to decide whether the version numbers are useful/meaningful template - class Version final : public IStringable + class Version final { static_assert((N > 0), "The number of version components may not be smaller than 1"); static_assert((Mandatory > 0), "The number of mandatory components may not be smaller than 1"); @@ -55,6 +53,11 @@ namespace Utils constexpr Version() = default; + Version(const QStringView string) + { + *this = fromString(string); + } + template constexpr Version(Ts ... params) requires std::conjunction_v...> @@ -108,7 +111,7 @@ namespace Utils return m_components.at(i); } - QString toString() const override + QString toString() const { // find the last one non-zero component int lastSignificantIndex = N - 1; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dbd47af58..aa7647cab 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,7 @@ include_directories("../src") set(testFiles testalgorithm.cpp testbittorrenttrackerentry.cpp + testconceptsstringable.cpp testglobal.cpp testorderedset.cpp testpath.cpp diff --git a/test/testconceptsstringable.cpp b/test/testconceptsstringable.cpp new file mode 100644 index 000000000..7a5ae5203 --- /dev/null +++ b/test/testconceptsstringable.cpp @@ -0,0 +1,73 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Mike Tzou (Chocobo1) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include +#include +#include + +#include "base/concepts/stringable.h" + +class TestConceptsStringable final : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(TestConceptsStringable) + +public: + TestConceptsStringable() = default; + +private slots: + void testStringable() const + { + struct A + { + }; + static_assert(!Stringable); + + struct B + { + B(QString) {} + }; + static_assert(!Stringable); + + struct C + { + QString toString() const { return {}; } + }; + static_assert(!Stringable); + + struct D + { + D(QString) {} + QString toString() const { return {}; } + }; + static_assert(Stringable); + } +}; + +QTEST_APPLESS_MAIN(TestConceptsStringable) +#include "testconceptsstringable.moc" diff --git a/test/testutilsversion.cpp b/test/testutilsversion.cpp index 9c07f81f7..fcabedca6 100644 --- a/test/testutilsversion.cpp +++ b/test/testutilsversion.cpp @@ -51,9 +51,9 @@ private slots: // Utils::Version<2, 0>(); using TwoDigits = Utils::Version<2, 1>; - TwoDigits(0); - TwoDigits(50); - TwoDigits(0, 1); + QCOMPARE(TwoDigits(0), TwoDigits(u"0"_s)); + QCOMPARE(TwoDigits(50), TwoDigits(u"50"_s)); + QCOMPARE(TwoDigits(0, 1), TwoDigits(u"0.1"_s)); using ThreeDigits = Utils::Version<3, 3>; // should not compile: @@ -61,7 +61,7 @@ private slots: // ThreeDigits(1, 2); // ThreeDigits(1.0, 2, 3); // ThreeDigits(1, 2, 3, 4); - ThreeDigits(1, 2, 3); + QCOMPARE(ThreeDigits(1, 2, 3), ThreeDigits(u"1.2.3"_s)); } void testIsValid() const