blob: 1683031bcf2015578f20650c68c1698224850c22 [file] [log] [blame]
/*
* Copyright 2024-present Alibaba Inc.
*
* Licensed 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