Specify interface requirements as an C++ concept

PR #19440.
This commit is contained in:
Chocobo1 2023-08-12 20:53:03 +08:00 committed by GitHub
parent 850da9dd83
commit 69d60b5f1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 144 additions and 28 deletions

View file

@ -40,6 +40,7 @@ add_library(qbt_base STATIC
bittorrent/torrentinfo.h bittorrent/torrentinfo.h
bittorrent/tracker.h bittorrent/tracker.h
bittorrent/trackerentry.h bittorrent/trackerentry.h
concepts/stringable.h
digest32.h digest32.h
exceptions.h exceptions.h
global.h global.h
@ -54,7 +55,6 @@ add_library(qbt_base STATIC
iconprovider.h iconprovider.h
indexrange.h indexrange.h
interfaces/iapplication.h interfaces/iapplication.h
interfaces/istringable.h
logger.h logger.h
net/dnsupdater.h net/dnsupdater.h
net/downloadhandlerimpl.h net/downloadhandlerimpl.h
@ -187,6 +187,7 @@ add_library(qbt_base STATIC
utils/random.cpp utils/random.cpp
utils/string.cpp utils/string.cpp
utils/thread.cpp utils/thread.cpp
utils/version.cpp
) )
target_link_libraries(qbt_base target_link_libraries(qbt_base

View file

@ -28,13 +28,13 @@
#pragma once #pragma once
#include <concepts>
class QString; class QString;
class IStringable template <typename T>
concept Stringable = requires (T t)
{ {
public: requires std::constructible_from<T, QString>;
virtual ~IStringable() = default; { t.toString() } -> std::same_as<QString>;
// requirement: T(const QString &) constructor for derived class `T` // TODO: try enforce it in C++20 concept
virtual QString toString() const = 0;
}; };

View file

@ -39,6 +39,7 @@
#include <QRegularExpression> #include <QRegularExpression>
#include <QStringView> #include <QStringView>
#include "base/concepts/stringable.h"
#include "base/global.h" #include "base/global.h"
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
@ -69,6 +70,9 @@ namespace
#endif #endif
} }
// `Path` should satisfy `Stringable` concept in order to be stored in settings as string
static_assert(Stringable<Path>);
Path::Path(const QString &pathStr) Path::Path(const QString &pathStr)
: m_pathStr {cleanPath(pathStr)} : m_pathStr {cleanPath(pathStr)}
{ {

View file

@ -36,11 +36,9 @@
#include "pathfwd.h" #include "pathfwd.h"
#include "base/interfaces/istringable.h"
class QStringView; class QStringView;
class Path final : public IStringable class Path final
{ {
public: public:
Path() = default; Path() = default;
@ -71,7 +69,7 @@ public:
Path relativePathOf(const Path &childPath) const; Path relativePathOf(const Path &childPath) const;
QString data() const; QString data() const;
QString toString() const override; QString toString() const;
std::filesystem::path toStdFsPath() const; std::filesystem::path toStdFsPath() const;
Path &operator/=(const Path &other); Path &operator/=(const Path &other);

View file

@ -38,7 +38,7 @@
#include <QVariant> #include <QVariant>
#include <QVariantHash> #include <QVariantHash>
#include "base/interfaces/istringable.h" #include "base/concepts/stringable.h"
#include "utils/string.h" #include "utils/string.h"
template <typename T> template <typename T>
@ -46,7 +46,7 @@ concept IsQFlags = std::same_as<T, QFlags<typename T::enum_type>>;
// There are 2 ways for class `T` provide serialization support into `SettingsStorage`: // 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 // 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 // 2. Otherwise, use `Q_DECLARE_METATYPE(T)` and let `QMetaType` handle the serialization
class SettingsStorage final : public QObject class SettingsStorage final : public QObject
{ {
@ -64,7 +64,12 @@ public:
template <typename T> template <typename T>
T loadValue(const QString &key, const T &defaultValue = {}) const T loadValue(const QString &key, const T &defaultValue = {}) const
{ {
if constexpr (std::is_base_of_v<IStringable, T>) if constexpr (std::same_as<T, QVariant>)
{
// fast path for loading QVariant
return loadValueImpl(key, defaultValue);
}
else if constexpr (Stringable<T>)
{ {
const QString value = loadValue(key, defaultValue.toString()); const QString value = loadValue(key, defaultValue.toString());
return T {value}; return T {value};
@ -79,11 +84,6 @@ public:
const typename T::Int value = loadValue(key, static_cast<typename T::Int>(defaultValue)); const typename T::Int value = loadValue(key, static_cast<typename T::Int>(defaultValue));
return T {value}; return T {value};
} }
else if constexpr (std::is_same_v<T, QVariant>)
{
// fast path for loading QVariant
return loadValueImpl(key, defaultValue);
}
else else
{ {
const QVariant value = loadValueImpl(key); const QVariant value = loadValueImpl(key);
@ -95,7 +95,9 @@ public:
template <typename T> template <typename T>
void storeValue(const QString &key, const T &value) void storeValue(const QString &key, const T &value)
{ {
if constexpr (std::is_base_of_v<IStringable, T>) if constexpr (std::same_as<T, QVariant>)
storeValueImpl(key, value);
else if constexpr (Stringable<T>)
storeValueImpl(key, value.toString()); storeValueImpl(key, value.toString());
else if constexpr (std::is_enum_v<T>) else if constexpr (std::is_enum_v<T>)
storeValueImpl(key, Utils::String::fromEnum(value)); storeValueImpl(key, Utils::String::fromEnum(value));

View file

@ -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<Utils::Version<1>>);

View file

@ -36,14 +36,12 @@
#include <QString> #include <QString>
#include <QStringView> #include <QStringView>
#include "base/interfaces/istringable.h"
namespace Utils namespace Utils
{ {
// This class provides a default implementation of `isValid()` that should work for most cases // 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 // It is ultimately up to the user to decide whether the version numbers are useful/meaningful
template <int N, int Mandatory = N> template <int N, int Mandatory = N>
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((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"); static_assert((Mandatory > 0), "The number of mandatory components may not be smaller than 1");
@ -55,6 +53,11 @@ namespace Utils
constexpr Version() = default; constexpr Version() = default;
Version(const QStringView string)
{
*this = fromString(string);
}
template <typename ... Ts> template <typename ... Ts>
constexpr Version(Ts ... params) constexpr Version(Ts ... params)
requires std::conjunction_v<std::is_convertible<Ts, int>...> requires std::conjunction_v<std::is_convertible<Ts, int>...>
@ -108,7 +111,7 @@ namespace Utils
return m_components.at(i); return m_components.at(i);
} }
QString toString() const override QString toString() const
{ {
// find the last one non-zero component // find the last one non-zero component
int lastSignificantIndex = N - 1; int lastSignificantIndex = N - 1;

View file

@ -8,6 +8,7 @@ include_directories("../src")
set(testFiles set(testFiles
testalgorithm.cpp testalgorithm.cpp
testbittorrenttrackerentry.cpp testbittorrenttrackerentry.cpp
testconceptsstringable.cpp
testglobal.cpp testglobal.cpp
testorderedset.cpp testorderedset.cpp
testpath.cpp testpath.cpp

View file

@ -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 <QObject>
#include <QString>
#include <QTest>
#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<A>);
struct B
{
B(QString) {}
};
static_assert(!Stringable<B>);
struct C
{
QString toString() const { return {}; }
};
static_assert(!Stringable<C>);
struct D
{
D(QString) {}
QString toString() const { return {}; }
};
static_assert(Stringable<D>);
}
};
QTEST_APPLESS_MAIN(TestConceptsStringable)
#include "testconceptsstringable.moc"

View file

@ -51,9 +51,9 @@ private slots:
// Utils::Version<2, 0>(); // Utils::Version<2, 0>();
using TwoDigits = Utils::Version<2, 1>; using TwoDigits = Utils::Version<2, 1>;
TwoDigits(0); QCOMPARE(TwoDigits(0), TwoDigits(u"0"_s));
TwoDigits(50); QCOMPARE(TwoDigits(50), TwoDigits(u"50"_s));
TwoDigits(0, 1); QCOMPARE(TwoDigits(0, 1), TwoDigits(u"0.1"_s));
using ThreeDigits = Utils::Version<3, 3>; using ThreeDigits = Utils::Version<3, 3>;
// should not compile: // should not compile:
@ -61,7 +61,7 @@ private slots:
// ThreeDigits(1, 2); // ThreeDigits(1, 2);
// ThreeDigits(1.0, 2, 3); // ThreeDigits(1.0, 2, 3);
// ThreeDigits(1, 2, 3, 4); // ThreeDigits(1, 2, 3, 4);
ThreeDigits(1, 2, 3); QCOMPARE(ThreeDigits(1, 2, 3), ThreeDigits(u"1.2.3"_s));
} }
void testIsValid() const void testIsValid() const