Program Listing for File strong.hpp

Return to documentation for file (SeQuant/core/utility/strong.hpp)

//
// Created by Eduard Valeyev on 7/24/24.
//

#ifndef SEQUANT_CORE_UTILITY_STRONG_HPP
#define SEQUANT_CORE_UTILITY_STRONG_HPP

#include <utility>

namespace sequant::detail {


template <typename T, typename Tag>
class strong_type_base {
  template <typename U, typename TagU>
  friend class strong_type_base;

 public:
  constexpr strong_type_base() noexcept(
      std::is_nothrow_default_constructible_v<T>) = default;
  constexpr strong_type_base(const strong_type_base&) noexcept(
      std::is_nothrow_copy_constructible_v<T>) = default;
  constexpr strong_type_base(strong_type_base&&) noexcept(
      std::is_nothrow_move_constructible_v<T>) = default;
  constexpr strong_type_base& operator=(const strong_type_base&) noexcept(
      std::is_nothrow_copy_assignable_v<T>) = default;
  constexpr strong_type_base& operator=(strong_type_base&&) noexcept(
      std::is_nothrow_move_assignable_v<T>) = default;

  template <
      typename U, typename TagU,
      typename = std::enable_if_t<
          (!std::is_same_v<T, U> ||
           !std::is_same_v<Tag, TagU>)&&std::is_constructible_v<T, const U&>>>
  explicit constexpr strong_type_base(
      const strong_type_base<U, TagU>&
          other) noexcept(std::is_nothrow_constructible_v<T, const U&>)
      : value_(other.value_) {}
  template <typename U, typename TagU,
            typename = std::enable_if_t<
                (!std::is_same_v<T, U> ||
                 !std::is_same_v<Tag, TagU>)&&std::is_constructible_v<T, U&&>>>
  explicit constexpr strong_type_base(
      strong_type_base<U, TagU>&&
          other) noexcept(std::is_nothrow_constructible_v<T, U&&>)
      : value_(std::move(other.value_)) {}
  template <
      typename U, typename TagU,
      typename = std::enable_if_t<
          (!std::is_same_v<T, U> ||
           !std::is_same_v<Tag, TagU>)&&std::is_assignable_v<T&, const U&>>>
  constexpr strong_type_base&
  operator=(const strong_type_base<U, TagU>& other) noexcept(
      std::is_nothrow_assignable_v<T, const U&>) {
    value_ = other.value_;
    return *this;
  }
  template <typename U, typename TagU,
            typename = std::enable_if_t<
                (!std::is_same_v<T, U> ||
                 !std::is_same_v<Tag, TagU>)&&std::is_assignable_v<T&, U&&>>>
  constexpr strong_type_base&
  operator=(strong_type_base<U, TagU>&& other) noexcept(
      std::is_nothrow_assignable_v<T, U&&>) {
    value_ = std::move(other.value_);
    return *this;
  }


  template <typename T_ = T,
            typename = std::enable_if_t<!std::is_same_v<
                meta::remove_cvref_t<T_>, meta::castable_to_any>>>
  explicit constexpr strong_type_base(const T& value) : value_(value) {}

  template <typename T_ = T,
            typename = std::enable_if_t<!std::is_same_v<
                meta::remove_cvref_t<T_>, meta::castable_to_any>>>
  explicit constexpr strong_type_base(T&& value) noexcept(
      std::is_nothrow_move_constructible<T>::value)
      : value_(std::move(value)) {}

  template <typename U, typename T_ = T,
            typename = std::enable_if_t<
                meta::is_range_v<T_> &&
                meta::is_statically_castable_v<U, meta::range_value_t<T_>>>>
  explicit constexpr strong_type_base(std::initializer_list<U> elements) {
    if constexpr (meta::has_memfn_push_back_v<T, U>)
      std::copy(elements.begin(), elements.end(), std::back_inserter(value_));
    else {
      assert(elements.size() == value_.size());
      std::copy(elements.begin(), elements.end(), value_.begin());
    }
  }

  template <typename... Elements,
            typename = std::enable_if_t<meta::is_std_array_v<T> &&
                                        meta::std_array_size_v<T> ==
                                            sizeof...(Elements)>>
  explicit constexpr strong_type_base(Elements&&... elements)
      : value_{std::forward<Elements>(elements)...} {}

  constexpr operator T&() & noexcept { return value_; }
  constexpr operator const T&() const& noexcept { return value_; }
  constexpr operator T&&() && noexcept { return std::move(value_); }
  constexpr operator const T&&() const&& noexcept { return std::move(value_); }

  constexpr T& value() & noexcept { return value_; }
  constexpr const T& value() const& noexcept { return value_; }
  constexpr T&& value() && noexcept { return std::move(value_); }
  constexpr const T&& value() const&& noexcept { return std::move(value_); }

  template <typename T_ = T,
            typename = std::enable_if_t<meta::has_memfn_size_v<const T_>>>
  decltype(auto) size() const {
    return value_.size();
  }

  template <typename T_ = T,
            typename = std::enable_if_t<meta::has_memfn_empty_v<const T_>>>
  decltype(auto) empty() const {
    return value_.empty();
  }

  template <typename T_ = T,
            typename = std::enable_if_t<meta::is_range_v<const T_>>>
  decltype(auto) begin() const {
    using ranges::begin;
    return begin(value_);
  }
  template <typename T_ = T, typename = std::enable_if_t<meta::is_range_v<T_>>>
  decltype(auto) begin() {
    using ranges::begin;
    return begin(value_);
  }
  template <typename T_ = T,
            typename = std::enable_if_t<meta::is_range_v<const T_>>>
  decltype(auto) end() const {
    using ranges::end;
    return end(value_);
  }
  template <typename T_ = T, typename = std::enable_if_t<meta::is_range_v<T_>>>
  decltype(auto) end() {
    using ranges::end;
    return end(value_);
  }

  template <typename T_ = T, typename = std::enable_if_t<
                                 meta::has_operator_subscript_v<const T_>>>
  decltype(auto) operator[](std::size_t i) const {
    return value_[i];
  }

  template <typename T_ = T,
            typename = std::enable_if_t<meta::has_operator_subscript_v<T_>>>
  decltype(auto) operator[](std::size_t i) {
    return value_[i];
  }

  template <typename T_ = T,
            typename = std::enable_if_t<meta::has_memfn_at_v<const T_>>>
  decltype(auto) at(std::size_t i) const {
    return value_.at(i);
  }

  template <typename T_ = T,
            typename = std::enable_if_t<meta::has_memfn_at_v<T_>>>
  decltype(auto) at(std::size_t i) {
    return value_.at(i);
  }

  template <typename T_ = T, typename U,
            typename = std::enable_if_t<meta::has_memfn_push_back_v<T_, U&&>>>
  decltype(auto) push_back(U&& u) const {
    return value_.push_back(std::forward<U>(u));
  }


  friend void swap(strong_type_base& a, strong_type_base& b) noexcept {
    using std::swap;
    swap(static_cast<T&>(a), static_cast<T&>(b));
  }

  template <typename U, typename TagU,
            typename = std::enable_if_t<meta::are_equality_comparable_v<T, U>>>
  friend bool operator==(const strong_type_base& a,
                         const strong_type_base<U, TagU>& b) noexcept {
    return static_cast<const T&>(a) == static_cast<const U&>(b);
  }

  template <typename U, typename TagU,
            typename = std::enable_if_t<meta::are_less_than_comparable_v<T, U>>>
  friend bool operator<(const strong_type_base& a,
                        const strong_type_base<U, TagU>& b) noexcept {
    return static_cast<const T&>(a) < static_cast<const U&>(b);
  }

  template <
      typename U, typename TagU,
      typename = std::enable_if_t<meta::are_greater_than_comparable_v<T, U> ||
                                  (meta::are_equality_comparable_v<T, U> &&
                                   meta::are_less_than_comparable_v<T, U>)>>
  friend bool operator>(const strong_type_base& a,
                        const strong_type_base<U, TagU>& b) noexcept {
    if constexpr (meta::are_greater_than_comparable_v<T, U>)
      return static_cast<const T&>(a) > static_cast<const U&>(b);
    else
      return !(static_cast<const T&>(a) < static_cast<const U&>(b) ||
               static_cast<const T&>(a) == static_cast<const U&>(b));
  }

  template <typename U, typename TagU,
            typename = meta::are_less_than_comparable<T, U>>
  friend std::common_type_t<T, U> max(
      const strong_type_base& a, const strong_type_base<U, TagU>& b) noexcept {
    return a < b ? b.value() : a.value();
  }

  template <typename U, typename TagU,
            typename = meta::are_less_than_comparable<T, U>>
  friend std::common_type_t<T, U> min(
      const strong_type_base& a, const strong_type_base<U, TagU>& b) noexcept {
    return a < b ? a.value() : b.value();
  }

  template <typename T_ = T,
            typename = std::enable_if_t<std::is_integral_v<T_> &&
                                        std::is_signed_v<T_>>>
  strong_type_base operator-() const noexcept {
    return strong_type_base(-value());
  }

 private:
  T value_{};
};  // class strong_type_base

}  // namespace sequant::detail

#ifndef DEFINE_STRONG_TYPE_FOR_RANGE
// N.B. default deduction guide with older gcc does not defer to the
// initializer_list guide, so end up with non-range instantiations clang and
// recent gcc (14) handle this fine ... this means we have to explicitly define
// the list of types that the default deduction guide will use as is (i.e.
// interpret as T)
#define DEFINE_STRONG_TYPE_FOR_RANGE(ID)                                       \
  template <typename T>                                                        \
  struct ID : detail::strong_type_base<T, ID<T>> {                             \
    using base_type = detail::strong_type_base<T, ID<T>>;                      \
    using base_type::base_type;                                                \
    using base_type::operator=;                                                \
  };                                                                           \
                                                                               \
  template <typename T>                                                        \
  ID(T&& t) -> ID<std::conditional_t<                                          \
      ((meta::is_range_v<meta::remove_cvref_t<T>> &&                           \
        !meta::is_char_range_v<meta::remove_cvref_t<T>>) ||                    \
       std::is_arithmetic_v<meta::remove_cvref_t<T>> ||                        \
       std::is_same_v<meta::remove_cvref_t<T>, IndexSpace>),                   \
      meta::remove_cvref_t<T>,                                                 \
      container::svector<                                                      \
          meta::literal_to_string_t<meta::remove_cvref_t<T>>>>>;               \
  template <typename T = meta::castable_to_any>                                \
  ID(std::initializer_list<T> t) -> ID<                                        \
      container::svector<meta::literal_to_string_t<meta::remove_cvref_t<T>>>>; \
  template <                                                                   \
      typename T, typename... U,                                               \
      typename = std::enable_if_t<                                             \
          sizeof...(U) != 0 &&                                                 \
          (std::is_same_v<meta::remove_cvref_t<T>, meta::remove_cvref_t<U>> && \
           ...)>>                                                              \
  ID(T&& t, U&&... rest)                                                       \
      -> ID<std::array<meta::literal_to_string_t<meta::remove_cvref_t<T>>,     \
                       1 + sizeof...(U)>>;                                     \
  template <typename T = meta::castable_to_any>                                \
  ID() -> ID<std::array<T, 0>>;
#endif  // DEFINE_STRONG_TYPE_FOR_RANGE

#ifndef DEFINE_STRONG_TYPE_FOR_INTEGER
#define DEFINE_STRONG_TYPE_FOR_INTEGER(ID, IntType)          \
  struct ID : detail::strong_type_base<IntType, ID> {        \
    using base_type = detail::strong_type_base<IntType, ID>; \
    using base_type::base_type;                              \
    using base_type::operator=;                              \
    using base_type::operator-;                              \
    ID(const base_type& b) noexcept : base_type(b) {}        \
    ID(base_type&& b) noexcept : base_type(std::move(b)) {}  \
  };
#endif  // DEFINE_STRONG_TYPE_FOR_INTEGER
#ifndef DEFINE_STRONG_TYPE_FOR_RANGE_AND_RANGESIZE
#define DEFINE_STRONG_TYPE_FOR_RANGE_AND_RANGESIZE(ID)               \
  DEFINE_STRONG_TYPE_FOR_INTEGER(SEQUANT_CONCAT(n, ID), std::size_t) \
  DEFINE_STRONG_TYPE_FOR_RANGE(ID)
#endif  // DEFINE_STRONG_TYPE_FOR_RANGE_AND_RANGESIZE

#endif  // SEQUANT_CORE_UTILITY_STRONG_HPP