File indexing completed on 2024-12-22 05:05:19
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 };