| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| #pragma once | |
| #include <cassert> | |
| #include <type_traits> | |
| #include <utility> | |
| #include "paimon/status.h" | |
| #include "paimon/traits.h" | |
| namespace paimon { | |
| /// A `Result<T>` object holds either a value of type T or a `Status` object explaining | |
| /// why such a value is not present. | |
| template <typename T> | |
| class PAIMON_MUST_USE_TYPE PAIMON_EXPORT Result { | |
| public: | |
| /// Destructor that properly cleans up the stored value or status. | |
| ~Result() { | |
| if (ok()) { | |
| data_.~T(); | |
| } | |
| status_.~Status(); | |
| } | |
| // NOLINTBEGIN(google-explicit-constructor, runtime/explicit) | |
| /// Construct a successful result with a copy of the given value. | |
| /// @param data The value to store in result. | |
| Result(const T& data) : status_(), data_(data) {} | |
| /// Construct a successful result by moving the given value. | |
| /// @param data The value to move into result. | |
| Result(T&& data) : status_(), data_(std::move(data)) {} | |
| /// Template constructor for converting compatible pointer types. | |
| /// Support T = std::unique_ptr<B> and U = std::unique_ptr<D> convert, where D is derived class | |
| /// of B also supports T = std::unique_ptr<B, AllocatorDelete> | |
| /// @param data The value to move into result. | |
| template <typename U, | |
| std::enable_if_t< | |
| is_pointer<U>::value && is_pointer<T>::value && | |
| std::is_convertible_v<value_type_traits_t<U>, value_type_traits_t<T>>>* = nullptr> | |
| Result(U&& data) : status_(), data_(std::move(data)) {} | |
| /// Construct a failed result with the given status. | |
| /// @param status The status object describing the error. | |
| Result(const Status& status) : status_(status) {} | |
| // NOLINTEND(google-explicit-constructor, runtime/explicit) | |
| /// Copy constructor. | |
| /// @param other The result to copy from. | |
| Result(const Result& other) { | |
| if (other.ok()) { | |
| MakeValue(other.data_); | |
| } | |
| MakeStatus(other.status_); | |
| } | |
| /// Move constructor. | |
| /// @param other The result to move from. | |
| Result(Result&& other) noexcept { | |
| if (other.ok()) { | |
| MakeValue(std::move(other.data_)); | |
| } | |
| // If we moved the status, the other status may become ok but the other | |
| // value hasn't been constructed => crash on other destructor. | |
| MakeStatus(other.status_); | |
| } | |
| // NOLINTBEGIN(google-explicit-constructor, runtime/explicit) | |
| /// Template move constructor for converting compatible `Result` types. | |
| /// @param other The result to move from. | |
| template <typename U, | |
| std::enable_if_t< | |
| is_pointer<U>::value && is_pointer<T>::value && | |
| std::is_convertible_v<value_type_traits_t<U>, value_type_traits_t<T>>>* = nullptr> | |
| Result(Result<U>&& other) noexcept { | |
| if (other.ok()) { | |
| MakeValue(std::move(other).value()); | |
| } | |
| // If we moved the status, the other status may become ok but the other | |
| // value hasn't been constructed => crash on other destructor. | |
| MakeStatus(other.status()); | |
| } | |
| // NOLINTEND(google-explicit-constructor, runtime/explicit) | |
| /// Copy assignment operator. | |
| /// @param other The result to copy from. | |
| /// @return Reference to this result. | |
| Result& operator=(const Result& other) { | |
| if (this == &other) { | |
| return *this; | |
| } | |
| if (other.ok()) { | |
| if (ok()) { | |
| data_ = other.data_; | |
| } else { | |
| MakeValue(other.value()); | |
| } | |
| } else { | |
| if (ok()) { | |
| data_.~T(); | |
| } | |
| } | |
| status_ = other.status_; | |
| return *this; | |
| } | |
| /// Move assignment operator. | |
| /// @param other The result to move from. | |
| /// @return Reference to this result. | |
| Result& operator=(Result&& other) { | |
| if (this == &other) { | |
| return *this; | |
| } | |
| if (other.ok()) { | |
| if (ok()) { | |
| data_ = std::move(other.data_); | |
| } else { | |
| MakeValue(std::move(other).value()); | |
| } | |
| } else { | |
| if (ok()) { | |
| data_.~T(); | |
| } | |
| } | |
| status_ = other.status_; | |
| return *this; | |
| } | |
| /// Template move assignment operator for converting compatible `Result` types. | |
| /// @param other The result to move from. | |
| /// @return Reference to this result. | |
| template <typename U, | |
| std::enable_if_t< | |
| is_pointer<U>::value && is_pointer<T>::value && | |
| std::is_convertible_v<value_type_traits_t<U>, value_type_traits_t<T>>>* = nullptr> | |
| Result& operator=(Result<U>&& other) { | |
| if (other.ok()) { | |
| if (ok()) { | |
| data_ = std::move(other.data_); | |
| } else { | |
| MakeValue(std::move(other).value()); | |
| } | |
| } else { | |
| if (ok()) { | |
| data_.~T(); | |
| } | |
| } | |
| status_ = other.status(); | |
| return *this; | |
| } | |
| /// Check if this result contains a valid value (i.e., the operation succeeded). | |
| /// @return `true` if the result contains a valid value, `false` if it contains an error status. | |
| inline bool ok() const { | |
| return status_.ok(); | |
| } | |
| /// Return the value stored in result. | |
| /// @return Const reference to the stored value. | |
| /// @note Calling this method when `ok()` is false results in undefined behavior. | |
| inline const T& value() const& { | |
| return data_; | |
| } | |
| /// Return the value stored in result. | |
| /// @return Rvalue reference to the stored value. | |
| /// @note Calling this method when `ok()` is false results in undefined behavior. | |
| inline T&& value() && { | |
| return std::move(data_); | |
| } | |
| /// Get the status of the result. | |
| /// @return Const reference to the status object. | |
| /// @note `status()` is always active, regardless of whether `ok()` is `true` or `false`. | |
| inline const Status& status() const { | |
| return status_; | |
| } | |
| /// Return the stored value if `ok()`, otherwise return the default value. | |
| /// @param default_value The value to return if this result contains an error. | |
| /// @return The stored value if `ok()`, otherwise the default_value. | |
| inline T value_or(T&& default_value) const& { | |
| if (ok()) { | |
| return data_; | |
| } else { | |
| return std::forward<T>(default_value); | |
| } | |
| } | |
| /// Return the stored value if `ok()`, otherwise return the default value (rvalue version). | |
| /// @param default_value The value to return if this result contains an error. | |
| /// @return The stored value if `ok()`, otherwise the default_value. | |
| inline T value_or(T&& default_value) && { | |
| if (ok()) { | |
| return std::move(data_); | |
| } else { | |
| return std::forward<T>(default_value); | |
| } | |
| } | |
| private: | |
| /// Construct the value(data_) through placement new with the passed arguments. | |
| /// @param args Arguments to forward to T's constructor. | |
| template <typename... Arg> | |
| void MakeValue(Arg&&... args) { | |
| new (&dummy_) T(std::forward<Arg>(args)...); | |
| } | |
| /// Construct the status (status_) through placement new with the passed arguments. | |
| /// @param args Arguments to forward to status constructor. | |
| template <typename... Arg> | |
| void MakeStatus(Arg&&... args) { | |
| new (&status_) Status(std::forward<Arg>(args)...); | |
| } | |
| private: | |
| // status_ will always be active after the constructor. | |
| // We make it a union to be able to initialize exactly how we need without | |
| // waste. | |
| // Eg. in the copy constructor we use the default constructor of `Status` in | |
| // the ok() path to avoid an extra Ref call. | |
| union { | |
| Status status_; | |
| }; | |
| union { | |
| // When T is const, we need some non-const object we can cast to void* for | |
| // the placement new. dummy_ is that object. | |
| std::aligned_storage_t<sizeof(T), alignof(T)> dummy_; | |
| T data_; | |
| }; | |
| }; | |
| namespace internal { | |
| /// Extract `Status` from `Result` (const lvalue reference version). | |
| /// @param res The result to extract the status from. | |
| /// @return Const reference to the status. | |
| template <typename T> | |
| inline const Status& GenericToStatus(const Result<T>& res) { | |
| return res.status(); | |
| } | |
| /// Extract `Status` from `Result` (rvalue reference version). | |
| /// @param res The result to extract the status from. | |
| /// @return status object (moved) | |
| template <typename T> | |
| inline Status GenericToStatus(Result<T>&& res) { | |
| return std::move(res).status(); | |
| } | |
| } // namespace internal | |
| #define PAIMON_ASSIGN_OR_RAISE_IMPL(result_name, lhs, rexpr) \ | |
| auto&& result_name = (rexpr); \ | |
| PAIMON_RETURN_IF_(!(result_name).ok(), (result_name).status(), PAIMON_STRINGIFY(rexpr)); \ | |
| lhs = std::move(result_name).value() | |
| #define PAIMON_ASSIGN_OR_RAISE_NAME(x, y) PAIMON_CONCAT(x, y) | |
| #define PAIMON_ASSIGN_OR_RAISE(lhs, rexpr) \ | |
| PAIMON_ASSIGN_OR_RAISE_IMPL(PAIMON_ASSIGN_OR_RAISE_NAME(_error_or_value, __COUNTER__), lhs, \ | |
| (rexpr)) | |
| } // namespace paimon |