traits.hpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. // Copyright 2018 Hans Dembinski
  2. //
  3. // Distributed under the Boost Software License, Version 1.0.
  4. // (See accompanying file LICENSE_1_0.txt
  5. // or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. #ifndef BOOST_HISTOGRAM_AXIS_TRAITS_HPP
  7. #define BOOST_HISTOGRAM_AXIS_TRAITS_HPP
  8. #include <boost/core/typeinfo.hpp>
  9. #include <boost/histogram/axis/option.hpp>
  10. #include <boost/histogram/detail/cat.hpp>
  11. #include <boost/histogram/detail/meta.hpp>
  12. #include <boost/histogram/fwd.hpp>
  13. #include <boost/throw_exception.hpp>
  14. #include <stdexcept>
  15. #include <utility>
  16. namespace boost {
  17. namespace histogram {
  18. namespace detail {
  19. template <class T>
  20. using static_options_impl = axis::option::bitset<T::options()>;
  21. template <class FIntArg, class FDoubleArg, class T>
  22. decltype(auto) value_method_switch(FIntArg&& iarg, FDoubleArg&& darg, const T& t) {
  23. return static_if<has_method_value<T>>(
  24. [](FIntArg&& iarg, FDoubleArg&& darg, const auto& t) {
  25. using A = remove_cvref_t<decltype(t)>;
  26. return static_if<std::is_same<arg_type<decltype(&A::value), 0>, int>>(
  27. std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
  28. },
  29. [](FIntArg&&, FDoubleArg&&, const auto& t) -> double {
  30. using A = remove_cvref_t<decltype(t)>;
  31. BOOST_THROW_EXCEPTION(std::runtime_error(detail::cat(
  32. boost::core::demangled_name(BOOST_CORE_TYPEID(A)), " has no value method")));
  33. #ifndef _MSC_VER // msvc warns about unreachable return
  34. return double{};
  35. #endif
  36. },
  37. std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
  38. }
  39. template <class R1, class R2, class FIntArg, class FDoubleArg, class T>
  40. R2 value_method_switch_with_return_type(FIntArg&& iarg, FDoubleArg&& darg, const T& t) {
  41. return static_if<has_method_value_with_convertible_return_type<T, R1>>(
  42. [](FIntArg&& iarg, FDoubleArg&& darg, const auto& t) -> R2 {
  43. using A = remove_cvref_t<decltype(t)>;
  44. return static_if<std::is_same<arg_type<decltype(&A::value), 0>, int>>(
  45. std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
  46. },
  47. [](FIntArg&&, FDoubleArg&&, const auto&) -> R2 {
  48. BOOST_THROW_EXCEPTION(std::runtime_error(
  49. detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(T)),
  50. " has no value method or return type is not convertible to ",
  51. boost::core::demangled_name(BOOST_CORE_TYPEID(R1)))));
  52. #ifndef _MSC_VER // msvc warns about unreachable return
  53. // conjure a value out of thin air to satisfy syntactic requirement
  54. return *reinterpret_cast<R2*>(0);
  55. #endif
  56. },
  57. std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
  58. }
  59. } // namespace detail
  60. namespace axis {
  61. namespace traits {
  62. /** Returns reference to metadata of an axis.
  63. If the expression x.metadata() for an axis instance `x` (maybe const) is valid, return
  64. the result. Otherwise, return a reference to a static instance of
  65. boost::histogram::axis::null_type.
  66. @param axis any axis instance
  67. */
  68. template <class Axis>
  69. decltype(auto) metadata(Axis&& axis) noexcept {
  70. return detail::static_if<
  71. detail::has_method_metadata<const detail::remove_cvref_t<Axis>>>(
  72. [](auto&& a) -> decltype(auto) { return a.metadata(); },
  73. [](auto &&) -> detail::copy_qualifiers<Axis, null_type> {
  74. static null_type m;
  75. return m;
  76. },
  77. std::forward<Axis>(axis));
  78. }
  79. /** Generates static axis option type for axis type.
  80. WARNING: Doxygen does not render the synopsis correctly. This is a templated using
  81. directive, which accepts axis type and returns boost::histogram::axis::option::bitset.
  82. If Axis::options() is valid and constexpr, return the corresponding option type.
  83. Otherwise, return boost::histogram::axis::option::growth_t, if the axis has a method
  84. `update`, else return boost::histogram::axis::option::none_t.
  85. @tparam Axis axis type
  86. */
  87. template <class Axis>
  88. using static_options = detail::mp_eval_or<
  89. mp11::mp_if<detail::has_method_update<detail::remove_cvref_t<Axis>>, option::growth_t,
  90. option::none_t>,
  91. detail::static_options_impl, detail::remove_cvref_t<Axis>>;
  92. /** Returns axis options as unsigned integer.
  93. If axis.options() is valid, return the result. Otherwise, return
  94. boost::histogram::axis::traits::static_options<decltype(axis)>::value.
  95. @param axis any axis instance
  96. */
  97. template <class Axis>
  98. constexpr unsigned options(const Axis& axis) noexcept {
  99. // cannot reuse static_options here, because this should also work for axis::variant
  100. return detail::static_if<detail::has_method_options<Axis>>(
  101. [](const auto& a) { return a.options(); },
  102. [](const auto&) { return static_options<Axis>::value; }, axis);
  103. }
  104. /** Returns axis size plus any extra bins for under- and overflow.
  105. @param axis any axis instance
  106. */
  107. template <class Axis>
  108. constexpr index_type extent(const Axis& axis) noexcept {
  109. const auto opt = options(axis);
  110. return axis.size() + (opt & option::underflow ? 1 : 0) +
  111. (opt & option::overflow ? 1 : 0);
  112. }
  113. /** Returns axis value for index.
  114. If the axis has no `value` method, throw std::runtime_error. If the method exists and
  115. accepts a floating point index, pass the index and return the result. If the method
  116. exists but accepts only integer indices, cast the floating point index to int, pass this
  117. index and return the result.
  118. @param axis any axis instance
  119. @param index floating point axis index
  120. */
  121. template <class Axis>
  122. decltype(auto) value(const Axis& axis, real_index_type index) {
  123. return detail::value_method_switch(
  124. [index](const auto& a) { return a.value(static_cast<int>(index)); },
  125. [index](const auto& a) { return a.value(index); }, axis);
  126. }
  127. /** Returns axis value for index if it is convertible to target type or throws.
  128. Like boost::histogram::axis::traits::value, but converts the result into the requested
  129. return type. If the conversion is not possible, throws std::runtime_error.
  130. @tparam Result requested return type
  131. @tparam Axis axis type
  132. @param axis any axis instance
  133. @param index floating point axis index
  134. */
  135. template <class Result, class Axis>
  136. Result value_as(const Axis& axis, real_index_type index) {
  137. return detail::value_method_switch_with_return_type<Result, Result>(
  138. [index](const auto& a) {
  139. return static_cast<Result>(a.value(static_cast<int>(index)));
  140. },
  141. [index](const auto& a) { return static_cast<Result>(a.value(index)); }, axis);
  142. }
  143. /** Returns axis index for value.
  144. Throws std::invalid_argument if the value argument is not implicitly convertible.
  145. @param axis any axis instance
  146. @param value argument to be passed to `index` method
  147. */
  148. template <class Axis, class U>
  149. auto index(const Axis& axis, const U& value) {
  150. using V = detail::arg_type<decltype(&Axis::index)>;
  151. return detail::static_if<std::is_convertible<U, V>>(
  152. [&value](const auto& axis) {
  153. using A = detail::remove_cvref_t<decltype(axis)>;
  154. using V2 = detail::arg_type<decltype(&A::index)>;
  155. return axis.index(static_cast<V2>(value));
  156. },
  157. [](const Axis&) -> index_type {
  158. BOOST_THROW_EXCEPTION(std::invalid_argument(
  159. detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(Axis)),
  160. ": cannot convert argument of type ",
  161. boost::core::demangled_name(BOOST_CORE_TYPEID(U)), " to ",
  162. boost::core::demangled_name(BOOST_CORE_TYPEID(V)))));
  163. #ifndef _MSC_VER // msvc warns about unreachable return
  164. return index_type{};
  165. #endif
  166. },
  167. axis);
  168. }
  169. /// @copydoc index(const Axis&, const U& value)
  170. template <class... Ts, class U>
  171. auto index(const variant<Ts...>& axis, const U& value) {
  172. return axis.index(value);
  173. }
  174. /** Returns pair of axis index and shift for the value argument.
  175. Throws `std::invalid_argument` if the value argument is not implicitly convertible to
  176. the argument expected by the `index` method. If the result of
  177. boost::histogram::axis::traits::static_options<decltype(axis)> has the growth flag set,
  178. call `update` method with the argument and return the result. Otherwise, call `index`
  179. and return the pair of the result and a zero shift.
  180. @param axis any axis instance
  181. @param value argument to be passed to `update` or `index` method
  182. */
  183. template <class Axis, class U>
  184. std::pair<int, int> update(Axis& axis, const U& value) {
  185. using V = detail::arg_type<decltype(&Axis::index)>;
  186. return detail::static_if<std::is_convertible<U, V>>(
  187. [&value](auto& a) {
  188. using A = detail::remove_cvref_t<decltype(a)>;
  189. return detail::static_if_c<static_options<A>::test(option::growth)>(
  190. [&value](auto& a) { return a.update(value); },
  191. [&value](auto& a) { return std::make_pair(a.index(value), 0); }, a);
  192. },
  193. [](Axis&) -> std::pair<index_type, index_type> {
  194. BOOST_THROW_EXCEPTION(std::invalid_argument(
  195. detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(Axis)),
  196. ": cannot convert argument of type ",
  197. boost::core::demangled_name(BOOST_CORE_TYPEID(U)), " to ",
  198. boost::core::demangled_name(BOOST_CORE_TYPEID(V)))));
  199. #ifndef _MSC_VER // msvc warns about unreachable return
  200. return std::make_pair(index_type{}, index_type{});
  201. #endif
  202. },
  203. axis);
  204. }
  205. /// @copydoc update(Axis& axis, const U& value)
  206. template <class... Ts, class U>
  207. auto update(variant<Ts...>& axis, const U& value) {
  208. return axis.update(value);
  209. }
  210. /** Returns bin width at axis index.
  211. If the axis has no `value` method, throw std::runtime_error. If the method exists and
  212. accepts a floating point index, return the result of `axis.value(index + 1) -
  213. axis.value(index)`. If the method exists but accepts only integer indices, return 0.
  214. @param axis any axis instance
  215. @param index bin index
  216. */
  217. template <class Axis>
  218. decltype(auto) width(const Axis& axis, index_type index) {
  219. return detail::value_method_switch(
  220. [](const auto&) { return 0; },
  221. [index](const auto& a) { return a.value(index + 1) - a.value(index); }, axis);
  222. }
  223. /** Returns bin width at axis index.
  224. Like boost::histogram::axis::traits::width, but converts the result into the requested
  225. return type. If the conversion is not possible, throw std::runtime_error.
  226. @param axis any axis instance
  227. @param index bin index
  228. */
  229. template <class Result, class Axis>
  230. Result width_as(const Axis& axis, index_type index) {
  231. return detail::value_method_switch_with_return_type<Result, Result>(
  232. [](const auto&) { return Result{}; },
  233. [index](const auto& a) {
  234. return static_cast<Result>(a.value(index + 1) - a.value(index));
  235. },
  236. axis);
  237. }
  238. } // namespace traits
  239. } // namespace axis
  240. } // namespace histogram
  241. } // namespace boost
  242. #endif