File indexing completed on 2024-05-12 05:26:16

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