packages feed

folly-clib-20260203.1245: folly/folly/memory/not_null.h

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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

/**
 * C++ Core Guideline's not_null PtrT>.
 *
 * not_null<PtrT> holds a pointer-like type PtrT which is not nullptr.
 *
 * not_null<T*> is a drop-in replacement for T* (as long as it's never null).
 * Specializations not_null_unique_ptr<T> and not_null_shared_ptr<T> are
 * drop-in replacements for unique_ptr<T> and shared_ptr<T>, respecitively.
 *
 * Example:
 *    void foo(not_null<int*> nnpi) {
 *      *nnpi = 7; // Safe, since `nnpi` is not null.
 *    }
 *
 *    void bar(not_null_shared_ptr<int> nnspi) {
 *      foo(nnsp.get());
 *    }
 *
 * Notes:
 *    - Constructing a not_null<PtrT> from a nullptr-equivalent argument throws
 *      a std::invalid_argument exception.
 *    - Cannot be used after move.
 *    - In debug mode, not_null checks that it is not null on all accesses,
 *      since use-after-move can cause the underlying PtrT to be null.
 */

#include <cstddef>
#include <functional>
#include <iosfwd>
#include <memory>
#include <type_traits>

#include <folly/Traits.h>

namespace folly {

namespace detail {
template <typename T>
struct is_not_null;
template <typename FromT, typename ToT>
struct is_not_null_convertible;
template <typename FromT, typename ToPtrT>
struct is_not_null_nothrow_constructible;
template <typename FromPtrT, typename ToT>
struct is_not_null_castable;
template <typename FromPtrT, typename ToT>
struct is_not_null_move_castable;

template <bool, template <typename...> class>
struct check_constraint_if_not {};

template <template <typename...> class DelayedConstraint>
struct check_constraint_if_not<true, DelayedConstraint> {
  template <typename... Args>
  constexpr static bool apply = true;
};

template <template <typename...> class DelayedConstraint>
struct check_constraint_if_not<false, DelayedConstraint> {
  template <typename... Args>
  constexpr static bool apply = DelayedConstraint<Args...>::value;
};

} // namespace detail

class guaranteed_not_null_provider {
 protected:
  struct guaranteed_not_null {};
};

class default_null_handler {
 public:
  // Handle termination when the invariant of the pointer being null is
  // violated, such as when doing debug checking after moving from a not_null
  // smart pointer.
  [[noreturn]] static inline void handle_terminate(const char* msg);

  // Handle when code attempts to construct or assign a not_null object from a
  // null pointer.
  [[noreturn]] static inline void handle_throw(const char* msg);
};

/**
 * not_null_base, the common interface for all not_null subclasses.
 *  - Implicitly constructs and casts just like a PtrT.
 *  - Has unwrap() function to access the underlying PtrT.
 */
template <typename PtrT, typename NullHandlerT>
class not_null_base : protected guaranteed_not_null_provider {
  template <bool>
  struct implicit_tag {};

 public:
  using pointer = PtrT;
  using element_type = typename std::pointer_traits<PtrT>::element_type;

  /**
   * Construction:
   *  - Throws std::invalid_argument if null.
   *  - Cannot default construct.
   *  - Cannot construct from nullptr.
   *  - Allows implicit construction iff PtrT allows implicit construction.
   *  - Construction from another not_null skips null check (in opt builds).
   */
  not_null_base() = delete;
  /* implicit */ not_null_base(std::nullptr_t) = delete;
  ~not_null_base() = default;

  // Copy and move constructors from other not_null_base types doesn't do any
  // null checking and thus will only throw if the underlying type's constructor
  // throws.
  not_null_base(const not_null_base& nn) = default;
  not_null_base(not_null_base&& nn) = default;

  template <
      typename U,
      typename =
          std::enable_if_t<detail::is_not_null_convertible<U&&, PtrT>::value>>
  /* implicit */ not_null_base(U&& u, implicit_tag<true> = {}) noexcept(
      detail::is_not_null_nothrow_constructible<U&&, PtrT>::value);

  template <
      typename U,
      typename =
          std::enable_if_t<!detail::is_not_null_convertible<U&&, PtrT>::value>>
  explicit not_null_base(U&& u, implicit_tag<false> = {}) noexcept(
      detail::is_not_null_nothrow_constructible<U&&, PtrT>::value);

  // Allow construction without a null check for trusted callsites.
  explicit not_null_base(
      PtrT&& ptr, guaranteed_not_null_provider::guaranteed_not_null) noexcept;

  /**
   * Assignment:
   *  - Due to implicit construction, just need to assign from self.
   *  - Cannot assign from nullptr.
   */
  not_null_base& operator=(std::nullptr_t) = delete;
  not_null_base& operator=(const not_null_base& nn) = default;
  not_null_base& operator=(not_null_base&& nn) = default;

  /**
   * Dereferencing:
   *  - Does not return mutable references, since that would allow the
   *    underlying pointer to be assigned to nullptr.
   */
  element_type& operator*() const noexcept;
  const PtrT& operator->() const noexcept;

  /**
   * Casting:
   *  - Implicit casting to PtrT allowed, so that not_null<PtrT> can be used
   *    wherever a PtrT is expected.
   *  - Does not return mutable references, since that would allow the
   *    underlying pointer to be assigned to nullptr.
   *  - Boolean cast is always true.
   */
  operator const PtrT&() const& noexcept;
  operator PtrT&&() && noexcept;

  template <
      typename U,
      typename = std::enable_if_t<detail::check_constraint_if_not<
          std::is_same_v<PtrT, folly::remove_cvref_t<U>>,
          detail::is_not_null_castable>::template apply<PtrT, U>>>
  operator U() const& noexcept(std::is_nothrow_constructible_v<U, const PtrT&>);

  template <
      typename U,
      typename = std::enable_if_t<detail::check_constraint_if_not<
          std::is_same_v<PtrT, folly::remove_cvref_t<U>>,
          detail::is_not_null_move_castable>::template apply<PtrT, U>>>
  operator U() && noexcept(std::is_nothrow_constructible_v<U, PtrT&&>);

  explicit inline operator bool() const noexcept { return true; }

  /**
   * Swap
   */
  void swap(not_null_base& other) noexcept;

  /**
   * Accessor:
   *  - Can explicitly access the underlying type via `unwrap`.
   *  - Does not return mutable references, since that would allow the
   *    underlying pointer to be assigned to nullptr.
   */
  const PtrT& unwrap() const& noexcept;
  PtrT&& unwrap() && noexcept;

 protected:
  void throw_if_null() const;
  template <typename T>
  static void throw_if_null(const T& ptr);
  template <typename T>
  static void terminate_if_null(const T& ptr);
  template <typename Deleter>
  static Deleter&& forward_or_throw_if_null(Deleter&& deleter);

  // Non-const accessor.
  PtrT& mutable_unwrap() noexcept;

 private:
  struct private_tag {};
  template <typename U>
  not_null_base(U&& u, private_tag);

  PtrT ptr_;
};

/**
 * not_null specializable class.
 *
 * Default implementation is not_null_base.
 */
template <typename PtrT, typename NullHandlerT = default_null_handler>
class not_null : public not_null_base<PtrT, NullHandlerT> {
 public:
  using pointer = typename not_null_base<PtrT, NullHandlerT>::pointer;
  using element_type = typename not_null_base<PtrT, NullHandlerT>::element_type;
  using not_null_base<PtrT, NullHandlerT>::not_null_base;
};

/**
 * not_null<std::unique_ptr<>> specialization.
 *
 * alias: not_null_unique_ptr
 *
 * Provides API compatibility with unique_ptr, except:
 *  - Pointer arguments must be non-null.
 *  - Cannot reset().
 *  - Functions are not noexcept, since debug-mode checks can throw exceptions.
 *  - Promotes returned pointers to be not_null pointers. Implicit casting
 *    allows these to be used in place of regular pointers.
 *
 * Notes:
 *  - Has make_not_null_unique, equivalent to std::make_unique
 */
template <typename T, typename Deleter, typename NullHandlerT>
class not_null<std::unique_ptr<T, Deleter>, NullHandlerT>
    : public not_null_base<std::unique_ptr<T, Deleter>, NullHandlerT> {
 public:
  using pointer =
      not_null<typename std::unique_ptr<T, Deleter>::pointer, NullHandlerT>;
  using element_type = typename std::unique_ptr<T, Deleter>::element_type;
  using deleter_type = typename std::unique_ptr<T, Deleter>::deleter_type;

  /**
   * Constructors. Most are inherited from not_null_base.
   */
  using not_null_base<std::unique_ptr<T, Deleter>, NullHandlerT>::not_null_base;

  not_null(pointer p, const Deleter& d);
  not_null(pointer p, Deleter&& d);

  /**
   * not_null_unique_ptr cannot be released - that would cause it to be null.
   */
  pointer release() = delete;

  /**
   * not_null_unique_ptr can only be reset to a non-null pointer.
   */
  void reset(std::nullptr_t) = delete;
  void reset(pointer ptr) noexcept;

  /**
   * get() returns a not_null (pointer type is not_null<T*>).
   *
   * Due to implicit casting, can still capture the result of get() as a regular
   * pointer type:
   *
   *   int* ptr = not_null_unique_ptr<int>(...).get(); // valid
   */
  pointer get() const noexcept;

  /**
   * get_deleter(): same as for unique_ptr.
   */
  Deleter& get_deleter() noexcept;
  const Deleter& get_deleter() const noexcept;
};

template <
    typename T,
    typename Deleter = std::default_delete<T>,
    typename NullHandlerT = default_null_handler>
using not_null_unique_ptr = not_null<std::unique_ptr<T, Deleter>, NullHandlerT>;

template <typename T, typename... Args>
not_null_unique_ptr<T> make_not_null_unique(Args&&... args);

/**
 * not_null<std::shared_ptr<>> specialization.
 *
 * alias: not_null_shared_ptr
 *
 * Provides API compatibility with shared_ptr, except:
 *  - Pointer arguments must be non-null.
 *  - Cannot reset().
 *  - Functions are not noexcept, since debug-mode checks can throw exceptions.
 *  - Promotes returned pointers to be not_null pointers. Implicit casting
 *    allows these to be used in place of regular pointers.
 *
 * Notes:
 *  - Has make_not_null_shared, equivalent to std::make_shared.
 */
template <typename T, typename NullHandlerT>
class not_null<std::shared_ptr<T>, NullHandlerT>
    : public not_null_base<std::shared_ptr<T>, NullHandlerT> {
 public:
  using element_type = typename std::shared_ptr<T>::element_type;
  using pointer = not_null<element_type*, NullHandlerT>;
  using weak_type = typename std::shared_ptr<T>::weak_type;

  /**
   * Constructors. Most are inherited from not_null_base.
   */
  using not_null_base<std::shared_ptr<T>, NullHandlerT>::not_null_base;

  template <typename U, typename Deleter>
  not_null(U* ptr, Deleter d);
  template <typename U, typename Deleter, typename UNullHandlerT>
  not_null(not_null<U*, UNullHandlerT> ptr, Deleter d);

  /**
   * Aliasing constructors.
   *
   * Notes:
   *  - The aliased shared_ptr argument, @r, is allowed to be null. The
   *    constructed object is not null iff @ptr is.
   *  - Don't template on the null handler of raw value not_null ptr wrappers
   *    so that we get automatic implicit conversion to the null handler of this
   *    destination type when aliasing raw pointer values.
   */
  template <typename U>
  not_null(
      const std::shared_ptr<U>& r,
      not_null<element_type*, NullHandlerT> ptr) noexcept;
  template <typename U, typename UNullHandlerT>
  not_null(
      const not_null<std::shared_ptr<U>, UNullHandlerT>& r,
      not_null<element_type*, NullHandlerT> ptr) noexcept;
  template <typename U>
  not_null(
      std::shared_ptr<U>&& r,
      not_null<element_type*, NullHandlerT> ptr) noexcept;
  template <typename U, typename UNullHandlerT>
  not_null(
      not_null<std::shared_ptr<U>, UNullHandlerT>&& r,
      not_null<element_type*, NullHandlerT> ptr) noexcept;

  /**
   * not_null_shared_ptr can only be reset to a non-null pointer.
   */
  void reset() = delete;
  template <typename U>
  void reset(U* ptr);
  template <typename U, typename UNullHandlerT>
  void reset(not_null<U*, UNullHandlerT> ptr) noexcept;
  template <typename U, typename Deleter>
  void reset(U* ptr, Deleter d);
  template <typename U, typename Deleter, typename UNullHandlerT>
  void reset(not_null<U*, UNullHandlerT> ptr, Deleter d);

  /**
   * get() returns a not_null.
   *
   * Due to implicit casting, can still capture the result of get() as a regular
   * pointer type:
   *
   *   int* ptr = not_null_shared_ptr<int>(...).get(); // valid
   */
  pointer get() const noexcept;

  /**
   * use_count()
   * owner_before()
   *
   * Same as shared_ptr.
   *
   * Notes:
   *  - unique() is deprecated in c++17, so is not implemented here. Can call
   *    not_null_shared_ptr.unwrap().unique() as a workaround, until unique()
   *    is removed in C++20.
   */
  long use_count() const noexcept;
  template <typename U>
  bool owner_before(const std::shared_ptr<U>& other) const noexcept;
  template <typename U, typename UNullHandlerT>
  bool owner_before(
      const not_null<std::shared_ptr<U>, UNullHandlerT>& other) const noexcept;
};

template <typename T, typename NullHandlerT = default_null_handler>
using not_null_shared_ptr = not_null<std::shared_ptr<T>, NullHandlerT>;

template <typename T, typename... Args>
not_null_shared_ptr<T> make_not_null_shared(Args&&... args);

template <typename T, typename Alloc, typename... Args>
not_null_shared_ptr<T> allocate_not_null_shared(
    const Alloc& alloc, Args&&... args);

/**
 * Comparison:
 *  - Forwards to underlying PtrT.
 *  - Works when one of the operands is not not_null.
 *  - Works when one of the operands is nullptr.
 */
#define FB_NOT_NULL_MK_OP(op)                                                 \
  template <typename PtrT, typename T, typename LhsNullHandlerT>              \
  bool operator op(const not_null<PtrT, LhsNullHandlerT>& lhs, const T& rhs); \
  template <                                                                  \
      typename PtrT,                                                          \
      typename T,                                                             \
      typename RhsNullHandlerT,                                               \
      typename = std::enable_if_t<!detail::is_not_null<T>::value>>            \
  bool operator op(const T& lhs, const not_null<PtrT, RhsNullHandlerT>& rhs);
FB_NOT_NULL_MK_OP(==)
FB_NOT_NULL_MK_OP(!=)
FB_NOT_NULL_MK_OP(<)
FB_NOT_NULL_MK_OP(<=)
FB_NOT_NULL_MK_OP(>)
FB_NOT_NULL_MK_OP(>=)
#undef FB_NOT_NULL_MK_OP

/**
 * Output:
 *  - Forwards to underlying PtrT.
 */
template <typename U, typename V, typename PtrT, typename NullHandlerT>
std::basic_ostream<U, V>& operator<<(
    std::basic_ostream<U, V>& os, const not_null<PtrT, NullHandlerT>& ptr);

/**
 * Swap
 */
template <typename PtrT>
void swap(not_null<PtrT>& lhs, not_null<PtrT>& rhs) noexcept;

/**
 * Getters
 */
template <typename Deleter, typename T, typename NullHandlerT>
Deleter* get_deleter(const not_null_shared_ptr<T, NullHandlerT>& ptr);

/**
 * Casting
 */
template <
    typename T,
    typename U,
    typename TNullHandlerT = default_null_handler,
    typename UNullHandlerT = default_null_handler>
not_null_shared_ptr<T, TNullHandlerT> static_pointer_cast(
    const not_null_shared_ptr<U, UNullHandlerT>& r);
template <
    typename T,
    typename U,
    typename TNullHandlerT = default_null_handler,
    typename UNullHandlerT = default_null_handler>
not_null_shared_ptr<T, TNullHandlerT> static_pointer_cast(
    not_null_shared_ptr<U, UNullHandlerT>&& r);
template <typename T, typename U, typename UNullHandlerT = default_null_handler>
std::shared_ptr<T> dynamic_pointer_cast(
    const not_null_shared_ptr<U, UNullHandlerT>& r);
template <typename T, typename U, typename UNullHandlerT = default_null_handler>
std::shared_ptr<T> dynamic_pointer_cast(
    not_null_shared_ptr<U, UNullHandlerT>&& r);
template <
    typename T,
    typename U,
    typename TNullHandlerT = default_null_handler,
    typename UNullHandlerT = default_null_handler>
not_null_shared_ptr<T, TNullHandlerT> const_pointer_cast(
    const not_null_shared_ptr<U, UNullHandlerT>& r);
template <
    typename T,
    typename U,
    typename TNullHandlerT = default_null_handler,
    typename UNullHandlerT = default_null_handler>
not_null_shared_ptr<T, TNullHandlerT> const_pointer_cast(
    not_null_shared_ptr<U, UNullHandlerT>&& r);
template <
    typename T,
    typename U,
    typename TNullHandlerT = default_null_handler,
    typename UNullHandlerT = default_null_handler>
not_null_shared_ptr<T, TNullHandlerT> reinterpret_pointer_cast(
    const not_null_shared_ptr<U, UNullHandlerT>& r);
template <
    typename T,
    typename U,
    typename TNullHandlerT = default_null_handler,
    typename UNullHandlerT = default_null_handler>
not_null_shared_ptr<T, TNullHandlerT> reinterpret_pointer_cast(
    not_null_shared_ptr<U, UNullHandlerT>&& r);

template <typename PtrT>
not_null(PtrT&&) -> not_null<std::remove_cv_t<std::remove_reference_t<PtrT>>>;

} // namespace folly

namespace std {
/**
 * Hashing:
 *  - Forwards to underlying PtrT.
 */
template <typename PtrT, typename NullHandlerT>
struct hash<::folly::not_null<PtrT, NullHandlerT>> : hash<PtrT> {};
} // namespace std

#include <folly/memory/not_null-inl.h>