blob: 18aa889e544799b97098363e16d00eace3d6395f [file] [log] [blame]
#pragma once
#include <c10/macros/Macros.h>
#include <c10/util/Exception.h>
#include <c10/util/in_place.h>
#include <type_traits>
namespace c10 {
/// Traits class describing how to borrow from T. As a synopsis, here
/// is how we might implement borrowing from an arbitrary type T using
/// a raw pointer to const:
///
/// template <typename T>
/// struct MaybeOwnedTraits {
/// using owned_type = T;
/// using borrow_type = const T*;
///
/// static borrow_type createBorrow(const owned_type& from) {
/// return &from;
/// }
///
/// static void assignBorrow(borrow_type& lhs, borrow_type rhs) {
/// lhs = rhs;
/// }
///
/// static void destroyBorrow(borrow_type& toDestroy) {}
///
/// static const owned_type& referenceFromBorrow(const borrow_type& borrow) {
/// return *borrow;
/// }
///
/// static const owned_type* pointerFromBorrow(const borrow_type& borrow) {
/// return borrow;
/// }
///
/// static bool debugBorrowIsValid(const borrow_type& borrow) {
/// return borrow != nullptr;
/// }
/// };
///
/// (This implementation is not in use because MaybeOwned is an unsafe
/// abstraction and we don't want to encourage it widely, just for
/// skipping reference counting in critical paths.)
///
/// For examples that are in use, see intrusive_ptr.h and TensorBody.h.
template <typename T>
struct MaybeOwnedTraits;
/// A smart pointer around either a borrowed or owned T. When
/// constructed with borrowed(), the caller MUST ensure that the
/// borrowed-from argument outlives this MaybeOwned<T>. Compare to
/// Rust's std::borrow::Cow
/// (https://doc.rust-lang.org/std/borrow/enum.Cow.html), but note
/// that it is probably not suitable for general use because C++ has
/// no borrow checking. Included here to support
/// Tensor::expect_contiguous.
template <typename T>
class MaybeOwned final {
using borrow_type = typename MaybeOwnedTraits<T>::borrow_type;
using owned_type = typename MaybeOwnedTraits<T>::owned_type;
bool isBorrowed_;
union {
borrow_type borrow_;
owned_type own_;
};
/// Don't use this; use borrowed() instead.
explicit MaybeOwned(const owned_type& t)
: isBorrowed_(true), borrow_(MaybeOwnedTraits<T>::createBorrow(t)) {}
/// Don't use this; use owned() instead.
explicit MaybeOwned(T&& t) noexcept(
std::is_nothrow_move_constructible<T>::value)
: isBorrowed_(false), own_(std::move(t)) {}
/// Don't use this; use owned() instead.
template <class... Args>
explicit MaybeOwned(in_place_t, Args&&... args)
: isBorrowed_(false), own_(std::forward<Args>(args)...) {}
public:
explicit MaybeOwned() : isBorrowed_(true), borrow_() {}
// Copying a borrow yields another borrow of the original, as with a
// T*. Copying an owned T yields another owned T for safety: no
// chains of borrowing by default! (Note you could get that behavior
// with MaybeOwned<T>::borrowed(*rhs) if you wanted it.)
MaybeOwned(const MaybeOwned& rhs) : isBorrowed_(rhs.isBorrowed_) {
if (C10_LIKELY(rhs.isBorrowed_)) {
MaybeOwnedTraits<T>::assignBorrow(borrow_, rhs.borrow_);
} else {
new (&own_) T(rhs.own_);
}
}
MaybeOwned& operator=(const MaybeOwned& rhs) {
if (this == &rhs) {
return *this;
}
if (C10_UNLIKELY(!isBorrowed_)) {
if (rhs.isBorrowed_) {
own_.~T();
MaybeOwnedTraits<T>::assignBorrow(borrow_, rhs.borrow_);
isBorrowed_ = true;
} else {
own_ = rhs.own_;
}
} else {
if (C10_LIKELY(rhs.isBorrowed_)) {
MaybeOwnedTraits<T>::assignBorrow(borrow_, rhs.borrow_);
} else {
MaybeOwnedTraits<T>::destroyBorrow(borrow_);
new (&own_) T(rhs.own_);
isBorrowed_ = false;
}
}
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(isBorrowed_ == rhs.isBorrowed_);
return *this;
}
MaybeOwned(MaybeOwned&& rhs) noexcept(
std::is_nothrow_move_constructible<T>::value)
: isBorrowed_(rhs.isBorrowed_) {
if (C10_LIKELY(rhs.isBorrowed_)) {
MaybeOwnedTraits<T>::assignBorrow(borrow_, rhs.borrow_);
} else {
new (&own_) T(std::move(rhs.own_));
}
}
MaybeOwned& operator=(MaybeOwned&& rhs) noexcept(
std::is_nothrow_move_assignable<T>::value) {
if (this == &rhs) {
return *this;
}
if (C10_UNLIKELY(!isBorrowed_)) {
if (rhs.isBorrowed_) {
own_.~T();
MaybeOwnedTraits<T>::assignBorrow(borrow_, rhs.borrow_);
isBorrowed_ = true;
} else {
own_ = std::move(rhs.own_);
}
} else {
if (C10_LIKELY(rhs.isBorrowed_)) {
MaybeOwnedTraits<T>::assignBorrow(borrow_, rhs.borrow_);
} else {
MaybeOwnedTraits<T>::destroyBorrow(borrow_);
new (&own_) T(std::move(rhs.own_));
isBorrowed_ = false;
}
}
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(isBorrowed_ == rhs.isBorrowed_);
return *this;
}
static MaybeOwned borrowed(const T& t) {
return MaybeOwned(t);
}
static MaybeOwned owned(T&& t) noexcept(
std::is_nothrow_move_constructible<T>::value) {
return MaybeOwned(std::move(t));
}
template <class... Args>
static MaybeOwned owned(in_place_t, Args&&... args) {
return MaybeOwned(in_place, std::forward<Args>(args)...);
}
~MaybeOwned() {
if (C10_UNLIKELY(!isBorrowed_)) {
own_.~T();
} else {
MaybeOwnedTraits<T>::destroyBorrow(borrow_);
}
}
// This is an implementation detail! You should know what you're doing
// if you are testing this. If you just want to guarantee ownership move
// this into a T
bool unsafeIsBorrowed() const {
return isBorrowed_;
}
const T& operator*() const& {
if (isBorrowed_) {
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(
MaybeOwnedTraits<T>::debugBorrowIsValid(borrow_));
}
return C10_LIKELY(isBorrowed_)
? MaybeOwnedTraits<T>::referenceFromBorrow(borrow_)
: own_;
}
const T* operator->() const {
if (isBorrowed_) {
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(
MaybeOwnedTraits<T>::debugBorrowIsValid(borrow_));
}
return C10_LIKELY(isBorrowed_)
? MaybeOwnedTraits<T>::pointerFromBorrow(borrow_)
: &own_;
}
// If borrowed, copy the underlying T. If owned, move from
// it. borrowed/owned state remains the same, and either we
// reference the same borrow as before or we are an owned moved-from
// T.
T operator*() && {
if (isBorrowed_) {
TORCH_INTERNAL_ASSERT_DEBUG_ONLY(
MaybeOwnedTraits<T>::debugBorrowIsValid(borrow_));
return MaybeOwnedTraits<T>::referenceFromBorrow(borrow_);
} else {
return std::move(own_);
}
}
};
} // namespace c10