Imported Upstream version 1.72.0
[platform/upstream/boost.git] / boost / histogram / axis / traits.hpp
index d907d6d..35b8941 100644 (file)
@@ -7,9 +7,9 @@
 #ifndef BOOST_HISTOGRAM_AXIS_TRAITS_HPP
 #define BOOST_HISTOGRAM_AXIS_TRAITS_HPP
 
+#include <boost/core/ignore_unused.hpp>
 #include <boost/histogram/axis/option.hpp>
 #include <boost/histogram/detail/args_type.hpp>
-#include <boost/histogram/detail/cat.hpp>
 #include <boost/histogram/detail/detect.hpp>
 #include <boost/histogram/detail/static_if.hpp>
 #include <boost/histogram/detail/try_cast.hpp>
@@ -21,6 +21,7 @@
 #include <boost/throw_exception.hpp>
 #include <boost/variant2/variant.hpp>
 #include <stdexcept>
+#include <string>
 #include <utility>
 
 namespace boost {
@@ -28,14 +29,34 @@ namespace histogram {
 namespace detail {
 
 template <class T>
-using static_options_impl = axis::option::bitset<T::options()>;
+using get_options_from_method = axis::option::bitset<T::options()>;
+
+template <class Axis>
+struct static_options_impl {
+  static_assert(std::is_same<std::decay_t<Axis>, Axis>::value,
+                "support of static_options for qualified types was removed, please use "
+                "static_options<std::decay_t<...>>");
+  using type = mp11::mp_eval_or<
+      mp11::mp_if<has_method_update<Axis>, axis::option::growth_t, axis::option::none_t>,
+      get_options_from_method, Axis>;
+};
+
+template <class T>
+using get_inclusive_from_method = std::integral_constant<bool, T::inclusive()>;
+
+template <class Axis>
+struct static_is_inclusive_impl {
+  using type = mp11::mp_eval_or<decltype(static_options_impl<Axis>::type::test(
+                                    axis::option::underflow | axis::option::overflow)),
+                                get_inclusive_from_method, Axis>;
+};
 
 template <class I, class D, class A>
 double value_method_switch_impl1(std::false_type, I&&, D&&, const A&) {
   // comma trick to make all compilers happy; some would complain about
   // unreachable code after the throw, others about a missing return
   return BOOST_THROW_EXCEPTION(
-             std::runtime_error(cat(type_name<A>(), " has no value method"))),
+             std::runtime_error(type_name<A>() + " has no value method")),
          double{};
 }
 
@@ -55,21 +76,16 @@ decltype(auto) value_method_switch(I&& i, D&& d, const A& a) {
 static axis::null_type null_value;
 
 struct variant_access {
-  template <class T, class T0, class Variant>
-  static auto get_if_impl(mp11::mp_list<T, T0>, Variant* v) noexcept {
-    return variant2::get_if<T>(&(v->impl));
-  }
-
-  template <class T, class T0, class Variant>
-  static auto get_if_impl(mp11::mp_list<T, T0*>, Variant* v) noexcept {
-    auto tp = variant2::get_if<mp11::mp_if<std::is_const<T0>, const T*, T*>>(&(v->impl));
-    return tp ? *tp : nullptr;
-  }
-
   template <class T, class Variant>
   static auto get_if(Variant* v) noexcept {
     using T0 = mp11::mp_first<std::decay_t<Variant>>;
-    return get_if_impl(mp11::mp_list<T, T0>{}, v);
+    return static_if<std::is_pointer<T0>>(
+        [](auto* vptr) {
+          using TP = mp11::mp_if<std::is_const<std::remove_pointer_t<T0>>, const T*, T*>;
+          auto ptp = variant2::get_if<TP>(vptr);
+          return ptp ? *ptp : nullptr;
+        },
+        [](auto* vptr) { return variant2::get_if<T>(vptr); }, &(v->impl));
   }
 
   template <class T0, class Visitor, class Variant>
@@ -97,29 +113,52 @@ struct variant_access {
 namespace axis {
 namespace traits {
 
-/** Returns reference to metadata of an axis.
+/** Get value type for axis type.
 
-  If the expression x.metadata() for an axis instance `x` (maybe const) is valid, return
-  the result. Otherwise, return a reference to a static instance of
-  boost::histogram::axis::null_type.
+  Doxygen does not render this well. This is a meta-function (template alias), it accepts
+  an axis type and returns the value type.
+*/
+template <class Axis>
+#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
+using value_type =
+    std::remove_cv_t<std::remove_reference_t<detail::arg_type<decltype(&Axis::index)>>>;
+#else
+struct value_type;
+#endif
 
-  @param axis any axis instance
+/** Whether axis is continuous or discrete.
+
+  Doxygen does not render this well. This is a meta-function (template alias), it accepts
+  an axis type and returns a compile-time boolean. If the boolean is true, the axis is
+  continuous. Otherwise it is discrete.
 */
 template <class Axis>
-decltype(auto) metadata(Axis&& axis) noexcept {
-  return detail::static_if<detail::has_method_metadata<std::decay_t<Axis>>>(
-      [](auto&& a) -> decltype(auto) { return a.metadata(); },
-      [](auto &&) -> mp11::mp_if<std::is_const<std::remove_reference_t<Axis>>,
-                                 axis::null_type const&, axis::null_type&> {
-        return detail::null_value;
-      },
-      std::forward<Axis>(axis));
-}
+#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
+using is_continuous = typename std::is_floating_point<traits::value_type<Axis>>::type;
+#else
+struct is_continuous;
+#endif
+
+/** Meta-function to detect whether an axis is reducible.
+
+  Doxygen does not render this well. This is a meta-function (template alias), it accepts
+  an axis type and represents compile-time boolean which is true or false, depending on
+  whether the axis can be reduced with boost::histogram::algorithm::reduce().
+
+  @tparam Axis axis type.
+ */
+template <class Axis>
+#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
+using is_reducible = std::is_constructible<Axis, const Axis&, axis::index_type,
+                                           axis::index_type, unsigned>;
+#else
+struct is_reducible;
+#endif
 
 /** Get static axis options for axis type.
 
-  Doxygen does not render this well. This is a meta-function, it accepts an axis
-  type and represents its boost::histogram::axis::option::bitset.
+  Doxygen does not render this well. This is a meta-function (template alias), it accepts
+  an axis type and returns the boost::histogram::axis::option::bitset.
 
   If Axis::options() is valid and constexpr, static_options is the corresponding
   option type. Otherwise, it is boost::histogram::axis::option::growth_t, if the
@@ -129,27 +168,71 @@ decltype(auto) metadata(Axis&& axis) noexcept {
 */
 template <class Axis>
 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
-using static_options =
-    mp11::mp_eval_or<mp11::mp_if<detail::has_method_update<std::decay_t<Axis>>,
-                                 option::growth_t, option::none_t>,
-                     detail::static_options_impl, std::decay_t<Axis>>;
+using static_options = typename detail::static_options_impl<Axis>::type;
 #else
 struct static_options;
 #endif
 
+/** Meta-function to detect whether an axis is inclusive.
+
+  Doxygen does not render this well. This is a meta-function (template alias), it accepts
+  an axis type and represents compile-time boolean which is true or false, depending on
+  whether the axis is inclusive or not.
+
+  An inclusive axis has a bin for every possible input value. A histogram which consists
+  only of inclusive axes can be filled more efficiently, since input values always
+  end up in a valid cell and there is no need to keep track of input tuples that need to
+  be discarded.
+
+  An axis with underflow and overflow bins is always inclusive, but an axis may be
+  inclusive under other conditions. The meta-function checks for the method `constexpr
+  static bool inclusive()`, and uses the result. If this method is not present, it uses
+  static_options<Axis> and checks whether the underflow and overflow bits are present.
+
+  @tparam axis type
+*/
+template <class Axis>
+#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
+using static_is_inclusive = typename detail::static_is_inclusive_impl<Axis>::type;
+#else
+struct static_is_inclusive;
+#endif
+
 /** Returns axis options as unsigned integer.
 
-  If axis.options() is valid, return the result. Otherwise, return
-  boost::histogram::axis::traits::static_options<decltype(axis)>::value.
+  If axis.options() is a valid expression, return the result. Otherwise, return
+  static_options<Axis>::value.
 
   @param axis any axis instance
 */
 template <class Axis>
 constexpr unsigned options(const Axis& axis) noexcept {
-  // cannot reuse static_options here, must also work for axis::variant
-  return detail::static_if<detail::has_method_options<Axis>>(
-      [](const auto& a) { return a.options(); },
-      [](const auto&) { return static_options<Axis>::value; }, axis);
+  boost::ignore_unused(axis);
+  return static_options<Axis>::value;
+}
+
+// specialization for variant
+template <class... Ts>
+unsigned options(const variant<Ts...>& axis) noexcept {
+  return axis.options();
+}
+
+/** Returns true if axis is inclusive or false.
+
+  See static_is_inclusive for details.
+
+  @param axis any axis instance
+*/
+template <class Axis>
+constexpr bool inclusive(const Axis& axis) noexcept {
+  boost::ignore_unused(axis);
+  return static_is_inclusive<Axis>::value;
+}
+
+// specialization for variant
+template <class... Ts>
+bool inclusive(const variant<Ts...>& axis) noexcept {
+  return axis.inclusive();
 }
 
 /** Returns axis size plus any extra bins for under- and overflow.
@@ -157,12 +240,31 @@ constexpr unsigned options(const Axis& axis) noexcept {
   @param axis any axis instance
 */
 template <class Axis>
-constexpr index_type extent(const Axis& axis) noexcept {
+index_type extent(const Axis& axis) noexcept {
   const auto opt = options(axis);
   return axis.size() + (opt & option::underflow ? 1 : 0) +
          (opt & option::overflow ? 1 : 0);
 }
 
+/** Returns reference to metadata of an axis.
+
+  If the expression x.metadata() for an axis instance `x` (maybe const) is valid, return
+  the result. Otherwise, return a reference to a static instance of
+  boost::histogram::axis::null_type.
+
+  @param axis any axis instance
+*/
+template <class Axis>
+decltype(auto) metadata(Axis&& axis) noexcept {
+  return detail::static_if<detail::has_method_metadata<std::decay_t<Axis>>>(
+      [](auto&& a) -> decltype(auto) { return a.metadata(); },
+      [](auto &&) -> mp11::mp_if<std::is_const<std::remove_reference_t<Axis>>,
+                                 axis::null_type const&, axis::null_type&> {
+        return detail::null_value;
+      },
+      std::forward<Axis>(axis));
+}
+
 /** Returns axis value for index.
 
   If the axis has no `value` method, throw std::runtime_error. If the method exists and
@@ -203,11 +305,10 @@ Result value_as(const Axis& axis, real_index_type index) {
   @param axis any axis instance
   @param value argument to be passed to `index` method
 */
-template <class Axis, class U,
-          class _V = std::decay_t<detail::arg_type<decltype(&Axis::index)>>>
-axis::index_type index(const Axis& axis,
-                       const U& value) noexcept(std::is_convertible<U, _V>::value) {
-  return axis.index(detail::try_cast<_V, std::invalid_argument>(value));
+template <class Axis, class U>
+axis::index_type index(const Axis& axis, const U& value) noexcept(
+    std::is_convertible<U, value_type<Axis>>::value) {
+  return axis.index(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
 }
 
 // specialization for variant
@@ -216,6 +317,25 @@ axis::index_type index(const variant<Ts...>& axis, const U& value) {
   return axis.index(value);
 }
 
+/** Return axis rank (how many arguments it processes).
+
+  @param axis any axis instance
+*/
+template <class Axis>
+constexpr unsigned rank(const Axis& axis) {
+  boost::ignore_unused(axis);
+  using T = value_type<Axis>;
+  // cannot use mp_eval_or since T could be a fixed-sized sequence
+  return mp11::mp_eval_if_not<detail::is_tuple<T>, mp11::mp_size_t<1>, mp11::mp_size,
+                              T>::value;
+}
+
+// specialization for variant
+template <class... Ts>
+unsigned rank(const axis::variant<Ts...>& axis) {
+  return detail::variant_access::visit([](const auto& a) { return rank(a); }, axis);
+}
+
 /** Returns pair of axis index and shift for the value argument.
 
   Throws `std::invalid_argument` if the value argument is not implicitly convertible to
@@ -227,13 +347,12 @@ axis::index_type index(const variant<Ts...>& axis, const U& value) {
   @param axis any axis instance
   @param value argument to be passed to `update` or `index` method
 */
-template <class Axis, class U,
-          class _V = std::decay_t<detail::arg_type<decltype(&Axis::index)>>>
+template <class Axis, class U>
 std::pair<index_type, index_type> update(Axis& axis, const U& value) noexcept(
-    std::is_convertible<U, _V>::value) {
+    std::is_convertible<U, value_type<Axis>>::value) {
   return detail::static_if_c<static_options<Axis>::test(option::growth)>(
       [&value](auto& a) {
-        return a.update(detail::try_cast<_V, std::invalid_argument>(value));
+        return a.update(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
       },
       [&value](auto& a) { return std::make_pair(index(a, value), index_type{0}); }, axis);
 }
@@ -279,22 +398,6 @@ Result width_as(const Axis& axis, index_type index) {
       axis);
 }
 
-/** Meta-function to detect whether an axis is reducible.
-
-  Doxygen does not render this well. This is a meta-function, it accepts an axis
-  type and represents std::true_type or std::false_type, depending on whether the axis can
-  be reduced with boost::histogram::algorithm::reduce().
-
-  @tparam Axis axis type.
- */
-template <class Axis>
-#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
-using is_reducible = std::is_constructible<Axis, const Axis&, axis::index_type,
-                                           axis::index_type, unsigned>;
-#else
-struct is_reducible;
-#endif
-
 } // namespace traits
 } // namespace axis
 } // namespace histogram