Implement QPromise<T>::convert<U>() (#41)

Converts the resolved value of `QPromise<T>` to the type `U`. Depending on types `T` and `U`, it performs a static cast, calls a converting constructor or tries to convert using `QVariant`.
This commit is contained in:
Dmitriy Purgin 2020-11-22 17:26:06 +01:00 committed by GitHub
parent 60b36e7a70
commit 0c3955cca5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 494 additions and 0 deletions

View File

@ -40,6 +40,7 @@ module.exports = {
title: 'QPromise', title: 'QPromise',
children: [ children: [
'/qtpromise/qpromise/constructor', '/qtpromise/qpromise/constructor',
'/qtpromise/qpromise/convert',
'/qtpromise/qpromise/delay', '/qtpromise/qpromise/delay',
'/qtpromise/qpromise/each', '/qtpromise/qpromise/each',
'/qtpromise/qpromise/fail', '/qtpromise/qpromise/fail',
@ -77,6 +78,7 @@ module.exports = {
children: [ children: [
'/qtpromise/exceptions/canceled', '/qtpromise/exceptions/canceled',
'/qtpromise/exceptions/context', '/qtpromise/exceptions/context',
'/qtpromise/exceptions/conversion',
'/qtpromise/exceptions/timeout', '/qtpromise/exceptions/timeout',
'/qtpromise/exceptions/undefined' '/qtpromise/exceptions/undefined'
] ]

View File

@ -3,6 +3,7 @@
## Functions ## Functions
- [`QPromise<T>::QPromise`](qpromise/constructor.md) - [`QPromise<T>::QPromise`](qpromise/constructor.md)
- [`QPromise<T>::convert`](qpromise/convert.md)
- [`QPromise<T>::delay`](qpromise/delay.md) - [`QPromise<T>::delay`](qpromise/delay.md)
- [`QPromise<T>::each`](qpromise/each.md) - [`QPromise<T>::each`](qpromise/each.md)
- [`QPromise<T>::fail`](qpromise/fail.md) - [`QPromise<T>::fail`](qpromise/fail.md)
@ -39,6 +40,7 @@
- [`QPromiseCanceledException`](exceptions/canceled.md) - [`QPromiseCanceledException`](exceptions/canceled.md)
- [`QPromiseContextException`](exceptions/context.md) - [`QPromiseContextException`](exceptions/context.md)
- [`QPromiseConversionException`](exceptions/conversion.md)
- [`QPromiseTimeoutException`](exceptions/timeout.md) - [`QPromiseTimeoutException`](exceptions/timeout.md)
- [`QPromiseUndefinedException`](exceptions/undefined.md) - [`QPromiseUndefinedException`](exceptions/undefined.md)

View File

@ -0,0 +1,13 @@
# QPromiseConversionException
*Since: 0.7.0*
This exception is thrown whenever a promise result conversion fails, for example:
```cpp
QPromise<QVariant> input = {...};
auto output = input.convert<int>()
.fail([](const QPromiseconversionException& e) {
// conversion may fail because input could not be converted to number
});
```

View File

@ -0,0 +1,124 @@
---
title: .convert
---
# QPromise::convert
*Since: 0.7.0*
```cpp
QPromise<T>::convert<U>() -> QPromise<U>
```
This method converts the resolved value of `QPromise<T>` to the type `U`. Depending on types `T`
and `U`, it performs a [static cast](https://en.cppreference.com/w/cpp/language/static_cast),
calls a [converting constructor](https://en.cppreference.com/w/cpp/language/converting_constructor),
or tries to convert using [QVariant](https://doc.qt.io/qt-5/qvariant.html).
If `T` and `U` are [fundamental types](https://en.cppreference.com/w/cpp/language/types) or
[enumerations](https://en.cppreference.com/w/cpp/language/enum), the result of the conversion is
the same as calling `static_cast<U>` for type `T`:
```cpp
QPromise<int> input = {...}
// output type: QPromise<double>
auto output = input.convert<double>();
output.then([](double value) {
// the value has been converted using static_cast
});
```
If `U` has a [converting constructor](https://en.cppreference.com/w/cpp/language/converting_constructor)
from `T`, i.e., a non-explicit constructor with a single argument accepting `T`, it is used to
convert the value:
```cpp
QPromise<QByteArray> input = {...}
// output type: QPromise<QString>
auto output = input.convert<QString>();
output.then([](const QString& value) {
// the value has been converted using static_cast that effectively calls QString(QByteArray)
});
```
::: tip NOTE
When using this method to convert to your own classes, make sure that the constructor meeting the
converting constructor criteria actually performs conversion.
:::
::: tip NOTE
If `U` is `void`, the resolved value of `QPromise<T>` is dropped.
:::
Calling this method for `QPromise<QVariant>` tries to convert the resolved `QVariant` to type `U`
using the `QVariant` [conversion algorithm](https://doc.qt.io/qt-5/qvariant.html#using-canconvert-and-convert-consecutively).
For example, this allows to convert a string contained in `QVariant` to number. If such a
conversion fails, the promise is rejected with
[`QPromiseConversionException`](../exceptions/conversion.md).
```cpp
// resolves to QVariant(int, 42) or QVariant(string, "foo")
QPromise<QVariant> input = {...};
auto output = input.convert<int>();
// output type: QPromise<int>
output.then([](int value) {
// input was QVariant(int, 42), value is 42
})
.fail(const QPromiseConversionException& e) {
// input was QVariant(string, "foo")
});
```
Conversion of `T` to `QVariant` using this method effectively calls `QVariant::fromValue<T>()`.
All custom types must be registered with
[`Q_DECLARE_METATYPE`](https://doc.qt.io/qt-5/qmetatype.html#Q_DECLARE_METATYPE) for this
conversion to work:
```cpp
struct Foo {};
Q_DECLARE_METATYPE(Foo);
QPromise<Foo> input = {...}
auto output = input.convert<QVariant>();
// output type: QPromise<QVariant>
output.then([](const QVariant& value) {
// value contains an instance of Foo
});
```
All other combinations of `T` and `U` are converted via `QVariant`. All non-Qt types should provide
a [conversion function](https://doc.qt.io/qt-5/qmetatype.html#registerConverter), otherwise the
promise is rejected with [`QPromiseConversionException`](../exceptions/conversion.md):
```cpp
struct Foo {};
Q_DECLARE_METATYPE(Foo);
QMetaType::registerConverter<Foo, QString>([](const Foo& foo) {
return QString{...};
});
QPromise<Foo> input = {...}
auto output = input.convert<QVariant>();
// output type: QPromise<QString>
output.then([](const QString& value) {
// value contains a result produced by the custom converter
})
.fail([](const QPromiseConversionException& e) {
// QVariant was unable to convert Foo to QString
})
```
::: warning IMPORTANT
Calling this method for `QPromise<void>` is not supported.
:::

View File

@ -111,6 +111,9 @@ public:
QPromise(F&& resolver) : QPromiseBase<T>(std::forward<F>(resolver)) QPromise(F&& resolver) : QPromiseBase<T>(std::forward<F>(resolver))
{ } { }
template<typename U>
inline QPromise<U> convert() const;
template<typename Functor> template<typename Functor>
inline QPromise<T> each(Functor fn); inline QPromise<T> each(Functor fn);

View File

@ -242,6 +242,13 @@ inline QPromise<QVector<T>> QPromise<T>::all(const Sequence<QPromise<T>, Args...
return QtPromise::all(promises); return QtPromise::all(promises);
} }
template<typename T>
template<typename U>
inline QPromise<U> QPromise<T>::convert() const
{
return QPromiseBase<T>::then(QtPromisePrivate::PromiseConverter<T, U>::create());
}
template<typename T> template<typename T>
inline QPromise<T> QPromise<T>::resolve(const T& value) inline QPromise<T> QPromise<T>::resolve(const T& value)
{ {

View File

@ -17,6 +17,7 @@
#include <QtCore/QSharedData> #include <QtCore/QSharedData>
#include <QtCore/QSharedPointer> #include <QtCore/QSharedPointer>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <QtCore/QVariant>
#include <QtCore/QVector> #include <QtCore/QVector>
namespace QtPromise { namespace QtPromise {
@ -30,6 +31,8 @@ class QPromiseResolve;
template<typename T> template<typename T>
class QPromiseReject; class QPromiseReject;
class QPromiseConversionException;
} // namespace QtPromise } // namespace QtPromise
namespace QtPromisePrivate { namespace QtPromisePrivate {
@ -553,6 +556,63 @@ struct PromiseInspect
} }
}; };
template<typename T, typename U, bool IsConvertibleViaStaticCast>
struct PromiseConverterBase;
template<typename T, typename U>
struct PromiseConverterBase<T, U, true>
{
static std::function<U(const T&)> create()
{
return [](const T& value) {
return static_cast<U>(value);
};
}
};
template<typename T, typename U>
struct PromiseConverterBase<T, U, false>
{
static std::function<U(const T&)> create()
{
return [](const T& value) {
auto tmp = QVariant::fromValue(value);
// https://doc.qt.io/qt-5/qvariant.html#using-canconvert-and-convert-consecutively
if (tmp.canConvert(qMetaTypeId<U>()) && tmp.convert(qMetaTypeId<U>())) {
return qvariant_cast<U>(tmp);
}
throw QtPromise::QPromiseConversionException{};
};
}
};
template<typename T>
struct PromiseConverterBase<T, QVariant, false>
{
static std::function<QVariant(const T&)> create()
{
return [](const T& value) {
return QVariant::fromValue(value);
};
}
};
template<typename T, typename U>
struct PromiseConverter
: PromiseConverterBase<T,
U,
// Fundamental types and converting constructors.
std::is_convertible<T, U>::value ||
// Conversion to void.
std::is_same<U, void>::value ||
// Conversion between enums and arithmetic types.
((std::is_enum<T>::value && std::is_arithmetic<U>::value)
|| (std::is_arithmetic<T>::value && std::is_enum<U>::value)
|| (std::is_enum<T>::value && std::is_enum<U>::value))>
{ };
} // namespace QtPromisePrivate } // namespace QtPromisePrivate
#endif // QTPROMISE_QPROMISE_H #endif // QTPROMISE_QPROMISE_H

View File

@ -35,6 +35,16 @@ public:
} }
}; };
class QPromiseConversionException : public QException
{
public:
void raise() const Q_DECL_OVERRIDE { throw *this; }
QPromiseConversionException* clone() const Q_DECL_OVERRIDE
{
return new QPromiseConversionException{*this};
}
};
class QPromiseTimeoutException : public QException class QPromiseTimeoutException : public QException
{ {
public: public:

View File

@ -20,6 +20,7 @@ class tst_exceptions : public QObject
private Q_SLOTS: private Q_SLOTS:
void canceled(); void canceled();
void context(); void context();
void conversion();
void timeout(); void timeout();
void undefined(); void undefined();
@ -53,6 +54,11 @@ void tst_exceptions::context()
verify<QPromiseContextException>(); verify<QPromiseContextException>();
} }
void tst_exceptions::conversion()
{
verify<QPromiseConversionException>();
}
void tst_exceptions::timeout() void tst_exceptions::timeout()
{ {
verify<QPromiseTimeoutException>(); verify<QPromiseTimeoutException>();

View File

@ -1,6 +1,7 @@
qtpromise_add_tests(qpromise qtpromise_add_tests(qpromise
SOURCES SOURCES
tst_construct.cpp tst_construct.cpp
tst_convert.cpp
tst_delay.cpp tst_delay.cpp
tst_each.cpp tst_each.cpp
tst_fail.cpp tst_fail.cpp

View File

@ -0,0 +1,266 @@
/*
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
*
* This source code is licensed under the MIT license found in
* the LICENSE file in the root directory of this source tree.
*/
#include "../shared/utils.h"
#include <QtPromise>
#include <QtTest>
#include <chrono>
using namespace QtPromise;
class tst_qpromise_convert : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void fulfillTAsU();
void fulfillTAsVoid();
void fulfillTAsQVariant();
void fulfillQVariantAsU();
void fulfillQVariantAsVoid();
void rejectUnconvertibleTypes();
};
QTEST_MAIN(tst_qpromise_convert)
#include "tst_convert.moc"
namespace {
struct Foo
{
Foo() = default;
Foo(int foo) : m_foo{foo} { }
bool operator==(const Foo& rhs) const { return m_foo == rhs.m_foo; }
int m_foo{-1};
};
struct Bar
{
Bar() = default;
Bar(const Foo& other) : m_bar{other.m_foo} { }
bool operator==(const Bar& rhs) const { return m_bar == rhs.m_bar; }
int m_bar{-1};
};
enum class Enum1 { Value0, Value1, Value2 };
enum class Enum2 { Value0, Value1, Value2 };
} // namespace
Q_DECLARE_METATYPE(Foo)
Q_DECLARE_METATYPE(Bar)
void tst_qpromise_convert::initTestCase()
{
// Register converter used by QVariant.
// https://doc.qt.io/qt-5/qmetatype.html#registerConverter
QMetaType::registerConverter<Foo, QString>([](const Foo& foo) {
return QString{"Foo{%1}"}.arg(foo.m_foo);
});
}
void tst_qpromise_convert::fulfillTAsU()
{
// Static cast between primitive types.
{
auto p = QtPromise::resolve(42.13).convert<int>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(waitForValue(p, -1), 42);
QVERIFY(p.isFulfilled());
}
// Convert enum class to int.
{
auto p = QtPromise::resolve(Enum1::Value1).convert<int>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(waitForValue(p, -1), 1);
QVERIFY(p.isFulfilled());
}
// Convert int to enum class.
{
auto p = QtPromise::resolve(1).convert<Enum1>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<Enum1>>::value));
QCOMPARE(waitForValue(p, Enum1::Value0), Enum1::Value1);
QVERIFY(p.isFulfilled());
}
// Convert between enums
{
auto p = QtPromise::resolve(Enum1::Value1).convert<Enum2>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<Enum2>>::value));
QCOMPARE(waitForValue(p, Enum2::Value0), Enum2::Value1);
QVERIFY(p.isFulfilled());
}
// Converting constructor for Qt types.
// https://en.cppreference.com/w/cpp/language/converting_constructor
{
auto p = QtPromise::resolve(QByteArray{"foo"}).convert<QString>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(waitForValue(p, QString{}), QString{"foo"});
QVERIFY(p.isFulfilled());
}
// Converting constructor for non-Qt types.
// https://en.cppreference.com/w/cpp/language/converting_constructor
{
auto p = QtPromise::resolve(Foo{42}).convert<Bar>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<Bar>>::value));
QCOMPARE(waitForValue(p, Bar{}), Bar{42});
QVERIFY(p.isFulfilled());
}
// Conversion of types Qt is aware of via QVariant.
{
auto p = QtPromise::resolve(42).convert<QString>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(waitForValue(p, QString{}), QString{"42"});
QVERIFY(p.isFulfilled());
}
// Conversion of a non-Qt type via QVariant.
// https://doc.qt.io/qt-5/qmetatype.html#registerConverter
{
auto p = QtPromise::resolve(Foo{42}).convert<QString>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(waitForValue(p, QString{}), QString{"Foo{42}"});
QVERIFY(p.isFulfilled());
}
}
void tst_qpromise_convert::fulfillTAsVoid()
{
auto p = QtPromise::resolve(42).convert<void>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(waitForValue(p, -1, 42), 42);
QVERIFY(p.isFulfilled());
}
void tst_qpromise_convert::fulfillTAsQVariant()
{
// Primitive type to QVariant.
{
auto p = QtPromise::resolve(42).convert<QVariant>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVariant>>::value));
QCOMPARE(waitForValue(p, QVariant{}), QVariant{42});
QVERIFY(p.isFulfilled());
}
// Non-Qt user-defined type to QVariant.
{
auto p = QtPromise::resolve(Foo{42}).convert<QVariant>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVariant>>::value));
QVariant value = waitForValue(p, QVariant{});
QCOMPARE(value, QVariant::fromValue(Foo{42}));
QCOMPARE(value.value<Foo>().m_foo, 42);
QVERIFY(p.isFulfilled());
}
}
void tst_qpromise_convert::fulfillQVariantAsU()
{
// Test whether a directly stored value can be extracted.
{
auto p = QtPromise::resolve(QVariant{42}).convert<int>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(waitForValue(p, -1), 42);
QVERIFY(p.isFulfilled());
}
// Test automatic conversion from string performed by QVariant.
// https://doc.qt.io/qt-5/qvariant.html#toInt
{
auto p = QtPromise::resolve(QVariant{"42"}).convert<int>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(waitForValue(p, -1), 42);
QVERIFY(p.isFulfilled());
}
// Non-Qt user-defined type
{
auto p = QtPromise::resolve(QVariant::fromValue(Foo{42})).convert<Foo>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<Foo>>::value));
QCOMPARE(waitForValue(p, Foo{}), Foo{42});
QVERIFY(p.isFulfilled());
}
}
void tst_qpromise_convert::fulfillQVariantAsVoid()
{
auto p = QtPromise::resolve(QVariant{42}).convert<void>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(waitForValue(p, -1, 42), 42);
QVERIFY(p.isFulfilled());
}
void tst_qpromise_convert::rejectUnconvertibleTypes()
{
// A string incompatible with int due to its value.
{
auto p = QtPromise::resolve(QString{"42foo"}).convert<int>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QVERIFY(waitForRejected<QPromiseConversionException>(p));
}
// A user-defined type unconvertible to string because there is no converter.
{
auto p = QtPromise::resolve(QVariant::fromValue(Bar{42})).convert<QString>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QVERIFY(waitForRejected<QPromiseConversionException>(p));
}
// A standard library type unconvertible to a primitive type because there is no converter.
{
auto p = QtPromise::resolve(std::vector<int>{42, -42}).convert<int>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QVERIFY(waitForRejected<QPromiseConversionException>(p));
}
}