blob: f87eb02d8fd0e54c89ff14c6864f1e3414ade850 [file] [log] [blame]
// Copyright 2022 The Pigweed Authors
//
// 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
//
// https://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 <type_traits>
#include <utility>
#include "pw_assert/assert.h"
#include "pw_status/status.h"
namespace pw {
template <typename T>
class [[nodiscard]] Result;
namespace internal_result {
// Detects whether `U` has conversion operator to `Result<T>`, i.e. `operator
// Result<T>()`.
template <typename T, typename U, typename = void>
struct HasConversionOperatorToResult : std::false_type {};
template <typename T, typename U>
void test(char (*)[sizeof(std::declval<U>().operator Result<T>())]);
template <typename T, typename U>
struct HasConversionOperatorToResult<T, U, decltype(test<T, U>(0))>
: std::true_type {};
// Detects whether `T` is constructible or convertible from `Result<U>`.
template <typename T, typename U>
using IsConstructibleOrConvertibleFromResult =
std::disjunction<std::is_constructible<T, Result<U>&>,
std::is_constructible<T, const Result<U>&>,
std::is_constructible<T, Result<U>&&>,
std::is_constructible<T, const Result<U>&&>,
std::is_convertible<Result<U>&, T>,
std::is_convertible<const Result<U>&, T>,
std::is_convertible<Result<U>&&, T>,
std::is_convertible<const Result<U>&&, T>>;
// Detects whether `T` is constructible or convertible or assignable from
// `Result<U>`.
template <typename T, typename U>
using IsConstructibleOrConvertibleOrAssignableFromResult =
std::disjunction<IsConstructibleOrConvertibleFromResult<T, U>,
std::is_assignable<T&, Result<U>&>,
std::is_assignable<T&, const Result<U>&>,
std::is_assignable<T&, Result<U>&&>,
std::is_assignable<T&, const Result<U>&&>>;
// Detects whether direct initializing `Result<T>` from `U` is ambiguous, i.e.
// when `U` is `Result<V>` and `T` is constructible or convertible from `V`.
template <typename T, typename U>
struct IsDirectInitializationAmbiguous
: public std::conditional_t<
std::is_same<std::remove_cv_t<std::remove_reference_t<U>>, U>::value,
std::false_type,
IsDirectInitializationAmbiguous<
T,
std::remove_cv_t<std::remove_reference_t<U>>>> {};
template <typename T, typename V>
struct IsDirectInitializationAmbiguous<T, Result<V>>
: public IsConstructibleOrConvertibleFromResult<T, V> {};
// Checks against the constraints of the direction initialization, i.e. when
// `Result<T>::Result(U&&)` should participate in overload resolution.
template <typename T, typename U>
using IsDirectInitializationValid = std::disjunction<
// Short circuits if T is basically U.
std::is_same<T, std::remove_cv_t<std::remove_reference_t<U>>>,
std::negation<std::disjunction<
std::is_same<Result<T>, std::remove_cv_t<std::remove_reference_t<U>>>,
std::is_same<Status, std::remove_cv_t<std::remove_reference_t<U>>>,
std::is_same<std::in_place_t,
std::remove_cv_t<std::remove_reference_t<U>>>,
IsDirectInitializationAmbiguous<T, U>>>>;
// This trait detects whether `Result<T>::operator=(U&&)` is ambiguous, which
// is equivalent to whether all the following conditions are met:
// 1. `U` is `Result<V>`.
// 2. `T` is constructible and assignable from `V`.
// 3. `T` is constructible and assignable from `U` (i.e. `Result<V>`).
// For example, the following code is considered ambiguous:
// (`T` is `bool`, `U` is `Result<bool>`, `V` is `bool`)
// Result<bool> s1 = true; // s1.ok() && s1.ValueOrDie() == true
// Result<bool> s2 = false; // s2.ok() && s2.ValueOrDie() == false
// s1 = s2; // ambiguous, `s1 = s2.ValueOrDie()` or `s1 = bool(s2)`?
template <typename T, typename U>
struct IsForwardingAssignmentAmbiguous
: public std::conditional_t<
std::is_same<std::remove_cv_t<std::remove_reference_t<U>>, U>::value,
std::false_type,
IsForwardingAssignmentAmbiguous<
T,
std::remove_cv_t<std::remove_reference_t<U>>>> {};
template <typename T, typename U>
struct IsForwardingAssignmentAmbiguous<T, Result<U>>
: public IsConstructibleOrConvertibleOrAssignableFromResult<T, U> {};
// Checks against the constraints of the forwarding assignment, i.e. whether
// `Result<T>::operator(U&&)` should participate in overload resolution.
template <typename T, typename U>
using IsForwardingAssignmentValid = std::disjunction<
// Short circuits if T is basically U.
std::is_same<T, std::remove_cv_t<std::remove_reference_t<U>>>,
std::negation<std::disjunction<
std::is_same<Result<T>, std::remove_cv_t<std::remove_reference_t<U>>>,
std::is_same<Status, std::remove_cv_t<std::remove_reference_t<U>>>,
std::is_same<std::in_place_t,
std::remove_cv_t<std::remove_reference_t<U>>>,
IsForwardingAssignmentAmbiguous<T, U>>>>;
// This trait is for determining if a given type is a Result.
template <typename T>
constexpr bool IsResult = false;
template <typename T>
constexpr bool IsResult<Result<T>> = true;
// This trait determines the return type of a given function without const,
// volatile or reference qualifiers.
template <typename Fn, typename T>
using InvokeResultType =
std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<Fn, T>>>;
PW_MODIFY_DIAGNOSTICS_PUSH();
PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wmaybe-uninitialized");
// Construct an instance of T in `p` through placement new, passing Args... to
// the constructor.
// This abstraction is here mostly for the gcc performance fix.
template <typename T, typename... Args>
void PlacementNew(void* p, Args&&... args) {
new (p) T(std::forward<Args>(args)...);
}
// Helper base class to hold the data and all operations.
// We move all this to a base class to allow mixing with the appropriate
// TraitsBase specialization.
//
// Pigweed addition: Specialize StatusOrData for trivially destructible types.
// This makes a Result usable in a constexpr statement.
//
// Note: in C++20, this entire file can be greatly simplfied with the requires
// statement.
template <typename T, bool = std::is_trivially_destructible<T>::value>
class StatusOrData;
// Place the implementation of StatusOrData in a macro so it can be shared
// between both specializations.
#define PW_RESULT_STATUS_OR_DATA_IMPL \
template <typename U, bool> \
friend class StatusOrData; \
\
public: \
StatusOrData() = delete; \
\
constexpr StatusOrData(const StatusOrData& other) \
: status_(other.status_), unused_() { \
if (other.ok()) { \
MakeValue(other.data_); \
} \
} \
\
constexpr StatusOrData(StatusOrData&& other) noexcept \
: status_(std::move(other.status_)), unused_() { \
if (other.ok()) { \
MakeValue(std::move(other.data_)); \
} \
} \
\
template <typename U> \
explicit constexpr StatusOrData(const StatusOrData<U>& other) { \
if (other.ok()) { \
MakeValue(other.data_); \
status_ = OkStatus(); \
} else { \
status_ = other.status_; \
} \
} \
\
template <typename U> \
explicit constexpr StatusOrData(StatusOrData<U>&& other) { \
if (other.ok()) { \
MakeValue(std::move(other.data_)); \
status_ = OkStatus(); \
} else { \
status_ = std::move(other.status_); \
} \
} \
\
template <typename... Args> \
explicit constexpr StatusOrData(std::in_place_t, Args&&... args) \
: status_(), data_(std::forward<Args>(args)...) {} \
\
explicit constexpr StatusOrData(const T& value) : status_(), data_(value) {} \
explicit constexpr StatusOrData(T&& value) \
: status_(), data_(std::move(value)) {} \
\
template <typename U, \
std::enable_if_t<std::is_constructible<Status, U&&>::value, int> = \
0> \
explicit constexpr StatusOrData(U&& v) \
: status_(std::forward<U>(v)), unused_() { \
PW_ASSERT(!status_.ok()); \
} \
\
constexpr StatusOrData& operator=(const StatusOrData& other) { \
if (this == &other) { \
return *this; \
} \
\
if (other.ok()) { \
Assign(other.data_); \
} else { \
AssignStatus(other.status_); \
} \
return *this; \
} \
\
constexpr StatusOrData& operator=(StatusOrData&& other) { \
if (this == &other) { \
return *this; \
} \
\
if (other.ok()) { \
Assign(std::move(other.data_)); \
} else { \
AssignStatus(std::move(other.status_)); \
} \
return *this; \
} \
\
template <typename U> \
constexpr void Assign(U&& value) { \
if (ok()) { \
data_ = std::forward<U>(value); \
} else { \
MakeValue(std::forward<U>(value)); \
status_ = OkStatus(); \
} \
} \
\
template <typename U> \
constexpr void AssignStatus(U&& v) { \
Clear(); \
status_ = static_cast<Status>(std::forward<U>(v)); \
PW_ASSERT(!status_.ok()); \
} \
\
constexpr bool ok() const { return status_.ok(); } \
\
protected: \
union { \
Status status_; \
}; \
\
struct Empty {}; \
union { \
Empty unused_; \
T data_; \
}; \
\
constexpr void Clear() { \
if (ok()) { \
data_.~T(); \
} \
} \
\
template <typename... Arg> \
void MakeValue(Arg&&... arg) { \
internal_result::PlacementNew<T>(&unused_, std::forward<Arg>(arg)...); \
} \
static_assert(true, "Macros must be terminated with a semicolon")
template <typename T>
class StatusOrData<T, true> {
PW_RESULT_STATUS_OR_DATA_IMPL;
};
template <typename T>
class StatusOrData<T, false> {
PW_RESULT_STATUS_OR_DATA_IMPL;
public:
// Add a destructor since T is not trivially destructible.
~StatusOrData() {
if (ok()) {
data_.~T();
}
}
};
#undef PW_RESULT_STATUS_OR_DATA_IMPL
PW_MODIFY_DIAGNOSTICS_POP();
// Helper base classes to allow implicitly deleted constructors and assignment
// operators in `Result`. For example, `CopyCtorBase` will explicitly delete
// the copy constructor when T is not copy constructible and `Result` will
// inherit that behavior implicitly.
template <typename T, bool = std::is_copy_constructible<T>::value>
struct CopyCtorBase {
CopyCtorBase() = default;
CopyCtorBase(const CopyCtorBase&) = default;
CopyCtorBase(CopyCtorBase&&) = default;
CopyCtorBase& operator=(const CopyCtorBase&) = default;
CopyCtorBase& operator=(CopyCtorBase&&) = default;
};
template <typename T>
struct CopyCtorBase<T, false> {
CopyCtorBase() = default;
CopyCtorBase(const CopyCtorBase&) = delete;
CopyCtorBase(CopyCtorBase&&) = default;
CopyCtorBase& operator=(const CopyCtorBase&) = default;
CopyCtorBase& operator=(CopyCtorBase&&) = default;
};
template <typename T, bool = std::is_move_constructible<T>::value>
struct MoveCtorBase {
MoveCtorBase() = default;
MoveCtorBase(const MoveCtorBase&) = default;
MoveCtorBase(MoveCtorBase&&) = default;
MoveCtorBase& operator=(const MoveCtorBase&) = default;
MoveCtorBase& operator=(MoveCtorBase&&) = default;
};
template <typename T>
struct MoveCtorBase<T, false> {
MoveCtorBase() = default;
MoveCtorBase(const MoveCtorBase&) = default;
MoveCtorBase(MoveCtorBase&&) = delete;
MoveCtorBase& operator=(const MoveCtorBase&) = default;
MoveCtorBase& operator=(MoveCtorBase&&) = default;
};
template <typename T,
bool = std::is_copy_constructible<T>::value&&
std::is_copy_assignable<T>::value>
struct CopyAssignBase {
CopyAssignBase() = default;
CopyAssignBase(const CopyAssignBase&) = default;
CopyAssignBase(CopyAssignBase&&) = default;
CopyAssignBase& operator=(const CopyAssignBase&) = default;
CopyAssignBase& operator=(CopyAssignBase&&) = default;
};
template <typename T>
struct CopyAssignBase<T, false> {
CopyAssignBase() = default;
CopyAssignBase(const CopyAssignBase&) = default;
CopyAssignBase(CopyAssignBase&&) = default;
CopyAssignBase& operator=(const CopyAssignBase&) = delete;
CopyAssignBase& operator=(CopyAssignBase&&) = default;
};
template <typename T,
bool = std::is_move_constructible<T>::value&&
std::is_move_assignable<T>::value>
struct MoveAssignBase {
MoveAssignBase() = default;
MoveAssignBase(const MoveAssignBase&) = default;
MoveAssignBase(MoveAssignBase&&) = default;
MoveAssignBase& operator=(const MoveAssignBase&) = default;
MoveAssignBase& operator=(MoveAssignBase&&) = default;
};
template <typename T>
struct MoveAssignBase<T, false> {
MoveAssignBase() = default;
MoveAssignBase(const MoveAssignBase&) = default;
MoveAssignBase(MoveAssignBase&&) = default;
MoveAssignBase& operator=(const MoveAssignBase&) = default;
MoveAssignBase& operator=(MoveAssignBase&&) = delete;
};
} // namespace internal_result
} // namespace pw