File indexing completed on 2024-04-28 05:26:02

0001 // SPDX-FileCopyrightText: 2018 Christian Mollekopf <mollekopf@kolabsys.com>
0002 // SPDX-License-Identifier: LGPL-2.0-or-later
0003 
0004 #pragma once
0005 
0006 #include <memory>
0007 #include <type_traits>
0008 #include <utility>
0009 
0010 // A somewhat implementation of the expected monad, proposed here:
0011 // https://isocpp.org/files/papers/n4015.pdf
0012 
0013 // A class used to differentiate errors and values when they are of the same type.
0014 template<typename Error>
0015 class Unexpected
0016 {
0017     static_assert(!std::is_same<Error, void>::value, "Cannot have an Unexpected void");
0018 
0019 public:
0020     Unexpected() = delete;
0021 
0022     constexpr explicit Unexpected(const Error &error)
0023         : mValue(error)
0024     {
0025     }
0026     constexpr explicit Unexpected(Error &&error)
0027         : mValue(std::move(error))
0028     {
0029     }
0030 
0031     // For implicit conversions when doing makeUnexpected(other)
0032     template<typename Other>
0033     constexpr explicit Unexpected(const Unexpected<Other> &error)
0034         : mValue(error.value())
0035     {
0036     }
0037     template<typename Other>
0038     constexpr explicit Unexpected(Unexpected<Other> &&error)
0039         : mValue(std::move(error.value()))
0040     {
0041     }
0042 
0043     constexpr const Error &value() const &
0044     {
0045         return mValue;
0046     }
0047     Error &value() &
0048     {
0049         return mValue;
0050     }
0051 
0052     constexpr const Error &&value() const &&
0053     {
0054         return std::move(mValue);
0055     }
0056     Error &&value() &&
0057     {
0058         return std::move(mValue);
0059     }
0060 
0061 private:
0062     Error mValue;
0063 };
0064 
0065 template<class Error>
0066 Unexpected<typename std::decay<Error>::type> makeUnexpected(Error &&e)
0067 {
0068     return Unexpected<typename std::decay<Error>::type>(std::forward<Error>(e));
0069 }
0070 
0071 template<typename Error>
0072 bool operator==(const Unexpected<Error> &lhs, const Unexpected<Error> &rhs)
0073 {
0074     return lhs.value() == rhs.value();
0075 }
0076 
0077 template<typename Error>
0078 bool operator!=(const Unexpected<Error> &lhs, const Unexpected<Error> &rhs)
0079 {
0080     return lhs.value() != rhs.value();
0081 }
0082 
0083 namespace detail
0084 {
0085 
0086 namespace tags
0087 {
0088 struct Expected {
0089 };
0090 struct Unexpected {
0091 };
0092 } // namespace tags
0093 
0094 // Write functions here when storage related and when Type != void
0095 template<typename Error, typename Type>
0096 struct StorageBase {
0097 protected:
0098     // To be able to define a copy constructor in a child class
0099     StorageBase()
0100     {
0101     }
0102 
0103     // Rule of 5 (copy constructors defined in StorageCopyConstructor) {{{
0104 
0105     StorageBase(StorageBase &&other)
0106         : mIsValue(other.mIsValue)
0107     {
0108         // This is a constructor, you have to construct object, not assign them
0109         // (hence the placement new)
0110         //
0111         // Here's the problem:
0112         //
0113         // Object that are part of a union are not initialized (which is
0114         // normal). If we replaced the placement new by a line like this:
0115         //
0116         // ```
0117         // mValue = other.mValue;
0118         // ```
0119         //
0120         // If overloaded, this will call `mValue.operator=(other.mValue);`, but
0121         // since we're in the constructor, mValue is not initialized. This can
0122         // cause big issues if `Type` / `Error` is not trivially (move)
0123         // assignable.
0124         //
0125         // And so, the placement new allows us to call the constructor of
0126         // `Type` or `Error` instead of its assignment operator.
0127         if (mIsValue) {
0128             new (std::addressof(mValue)) Type(std::move(other.mValue));
0129         } else {
0130             new (std::addressof(mError)) Unexpected<Error>(std::move(other.mError));
0131         }
0132     }
0133 
0134     StorageBase &operator=(StorageBase &&other)
0135     {
0136         this->~StorageBase();
0137         mIsValue = other.mIsValue;
0138         if (mIsValue) {
0139             mValue = std::move(other.mValue);
0140         } else {
0141             mError = std::move(other.mError);
0142         }
0143         return *this;
0144     }
0145 
0146     ~StorageBase()
0147     {
0148         if (mIsValue) {
0149             mValue.~Type();
0150         } else {
0151             mError.~Unexpected<Error>();
0152         }
0153     }
0154 
0155     // }}}
0156 
0157     template<typename... Args>
0158     constexpr StorageBase(tags::Expected, Args &&...args)
0159         : mValue(std::forward<Args>(args)...)
0160         , mIsValue(true)
0161     {
0162     }
0163 
0164     template<typename... Args>
0165     constexpr StorageBase(tags::Unexpected, Args &&...args)
0166         : mError(std::forward<Args>(args)...)
0167         , mIsValue(false)
0168     {
0169     }
0170 
0171     union {
0172         Unexpected<Error> mError;
0173         Type mValue;
0174     };
0175     bool mIsValue;
0176 };
0177 
0178 // Write functions here when storage related and when Type == void
0179 template<typename Error>
0180 struct StorageBase<Error, void> {
0181 protected:
0182     constexpr StorageBase(tags::Expected)
0183         : mIsValue(true)
0184     {
0185     }
0186 
0187     template<typename... Args>
0188     constexpr StorageBase(tags::Unexpected, Args &&...args)
0189         : mError(std::forward<Args>(args)...)
0190         , mIsValue(false)
0191     {
0192     }
0193 
0194     Unexpected<Error> mError;
0195     bool mIsValue;
0196 };
0197 
0198 // Struct used to add the copy constructor / assignment only if both types are copy constructible
0199 template<typename Error, typename Type, bool both_copy_constructible = std::is_copy_constructible<Error>::value &&std::is_copy_constructible<Type>::value>
0200 struct StorageCopyConstructor;
0201 
0202 template<typename Error, typename Type>
0203 struct StorageCopyConstructor<Error, Type, true> : StorageBase<Error, Type> {
0204 protected:
0205     using StorageBase<Error, Type>::StorageBase;
0206 
0207     StorageCopyConstructor(const StorageCopyConstructor &other)
0208         : StorageBase<Error, Type>()
0209     {
0210         // If you're thinking WTF, see the comment in the move constructor above.
0211         this->mIsValue = other.mIsValue;
0212         if (this->mIsValue) {
0213             new (std::addressof(this->mValue)) Type(other.mValue);
0214         } else {
0215             new (std::addressof(this->mError)) Unexpected<Error>(other.mError);
0216         }
0217     }
0218 
0219     StorageCopyConstructor &operator=(const StorageCopyConstructor &other)
0220     {
0221         this->mIsValue = other.mIsValue;
0222         if (this->mIsValue) {
0223             this->mValue = other.mValue;
0224         } else {
0225             this->mError = other.mError;
0226         }
0227         return *this;
0228     }
0229 };
0230 
0231 template<typename Error, typename Type>
0232 struct StorageCopyConstructor<Error, Type, false> : StorageBase<Error, Type> {
0233 protected:
0234     using StorageBase<Error, Type>::StorageBase;
0235 };
0236 
0237 // Write functions here when storage related, whether Type is void or not
0238 template<typename Error, typename Type>
0239 struct Storage : StorageCopyConstructor<Error, Type> {
0240 protected:
0241     // Forward the construction to StorageBase
0242     using StorageCopyConstructor<Error, Type>::StorageCopyConstructor;
0243 };
0244 
0245 // Write functions here when dev API related and when Type != void
0246 template<typename Error, typename Type>
0247 struct ExpectedBase : detail::Storage<Error, Type> {
0248     constexpr ExpectedBase()
0249         : detail::Storage<Error, Type>(detail::tags::Expected{})
0250     {
0251     }
0252 
0253     template<typename OtherError>
0254     constexpr ExpectedBase(const Unexpected<OtherError> &error)
0255         : detail::Storage<Error, Type>(detail::tags::Unexpected{}, error)
0256     {
0257     }
0258     template<typename OtherError>
0259     constexpr ExpectedBase(Unexpected<OtherError> &&error)
0260         : detail::Storage<Error, Type>(detail::tags::Unexpected{}, std::move(error))
0261     {
0262     }
0263 
0264     constexpr ExpectedBase(const Type &value)
0265         : detail::Storage<Error, Type>(detail::tags::Expected{}, value)
0266     {
0267     }
0268     constexpr ExpectedBase(Type &&value)
0269         : detail::Storage<Error, Type>(detail::tags::Expected{}, std::move(value))
0270     {
0271     }
0272 
0273     // Warning: will crash if this is an error. You should always check this is
0274     // an expected value before calling `.value()`
0275     constexpr const Type &value() const &
0276     {
0277         // FIXME: Q_ASSERT cannot be used in a constexpr with qt 5.9. See also:
0278         // https://git.qt.io/consulting-usa/qtbase-xcb-rendering/commit/8ea27bb1c669e21100a6a042b0378b3346bdf671 Q_ASSERT(this->mIsValue);
0279         return this->mValue;
0280     }
0281     Type &&value() &&
0282     {
0283         Q_ASSERT(this->mIsValue);
0284         return std::move(this->mValue);
0285     }
0286 };
0287 
0288 // Write functions here when dev API related and when Type == void
0289 template<typename Error>
0290 struct ExpectedBase<Error, void> : Storage<Error, void> {
0291     // Rewrite constructors for unexpected because Expected doesn't have direct access to it.
0292     template<typename OtherError>
0293     constexpr ExpectedBase(const Unexpected<OtherError> &error)
0294         : Storage<Error, void>(tags::Unexpected{}, error)
0295     {
0296     }
0297     template<typename OtherError>
0298     constexpr ExpectedBase(Unexpected<OtherError> &&error)
0299         : Storage<Error, void>(tags::Unexpected{}, std::move(error))
0300     {
0301     }
0302 };
0303 
0304 } // namespace detail
0305 
0306 // Write functions here when dev API related, whether Type is void or not
0307 template<typename Error, typename Type = void>
0308 class Expected : public detail::ExpectedBase<Error, Type>
0309 {
0310     static_assert(!std::is_same<Error, void>::value, "Expected with void Error is not implemented");
0311 
0312 public:
0313     using detail::ExpectedBase<Error, Type>::ExpectedBase;
0314 
0315     constexpr const Error &error() const &
0316     {
0317         return this->mError.value();
0318     }
0319 
0320     constexpr bool isValue() const
0321     {
0322         return this->mIsValue;
0323     }
0324     constexpr explicit operator bool() const
0325     {
0326         return this->mIsValue;
0327     }
0328 };