• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
7 #ifndef BOOST_HISTOGRAM_AXIS_TRAITS_HPP
8 #define BOOST_HISTOGRAM_AXIS_TRAITS_HPP
9 
10 #include <boost/histogram/axis/option.hpp>
11 #include <boost/histogram/detail/args_type.hpp>
12 #include <boost/histogram/detail/detect.hpp>
13 #include <boost/histogram/detail/priority.hpp>
14 #include <boost/histogram/detail/static_if.hpp>
15 #include <boost/histogram/detail/try_cast.hpp>
16 #include <boost/histogram/detail/type_name.hpp>
17 #include <boost/histogram/fwd.hpp>
18 #include <boost/mp11/algorithm.hpp>
19 #include <boost/mp11/list.hpp>
20 #include <boost/mp11/utility.hpp>
21 #include <boost/throw_exception.hpp>
22 #include <boost/variant2/variant.hpp>
23 #include <stdexcept>
24 #include <string>
25 #include <utility>
26 
27 namespace boost {
28 namespace histogram {
29 namespace detail {
30 
31 static axis::null_type null_value;
32 
33 template <class Axis>
34 struct value_type_deducer {
35   using type =
36       std::remove_cv_t<std::remove_reference_t<detail::arg_type<decltype(&Axis::index)>>>;
37 };
38 
39 template <class Axis>
40 auto traits_options(priority<2>) -> axis::option::bitset<Axis::options()>;
41 
42 template <class Axis>
43 auto traits_options(priority<1>) -> decltype(&Axis::update, axis::option::growth_t{});
44 
45 template <class Axis>
46 auto traits_options(priority<0>) -> axis::option::none_t;
47 
48 template <class Axis>
49 auto traits_is_inclusive(priority<1>) -> std::integral_constant<bool, Axis::inclusive()>;
50 
51 template <class Axis>
52 auto traits_is_inclusive(priority<0>)
53     -> decltype(traits_options<Axis>(priority<2>{})
54                     .test(axis::option::underflow | axis::option::overflow));
55 
56 template <class Axis>
57 auto traits_is_ordered(priority<1>) -> std::integral_constant<bool, Axis::ordered()>;
58 
59 template <class Axis, class ValueType = typename value_type_deducer<Axis>::type>
60 auto traits_is_ordered(priority<0>) -> typename std::is_arithmetic<ValueType>::type;
61 
62 template <class I, class D, class A,
63           class J = std::decay_t<arg_type<decltype(&A::value)>>>
value_method_switch(I && i,D && d,const A & a,priority<1>)64 decltype(auto) value_method_switch(I&& i, D&& d, const A& a, priority<1>) {
65   return static_if<std::is_same<J, axis::index_type>>(std::forward<I>(i),
66                                                       std::forward<D>(d), a);
67 }
68 
69 template <class I, class D, class A>
value_method_switch(I &&,D &&,const A &,priority<0>)70 double value_method_switch(I&&, D&&, const A&, priority<0>) {
71   // comma trick to make all compilers happy; some would complain about
72   // unreachable code after the throw, others about a missing return
73   return BOOST_THROW_EXCEPTION(
74              std::runtime_error(type_name<A>() + " has no value method")),
75          double{};
76 }
77 
78 struct variant_access {
79   template <class T, class Variant>
get_ifboost::histogram::detail::variant_access80   static auto get_if(Variant* v) noexcept {
81     using T0 = mp11::mp_first<std::decay_t<Variant>>;
82     return static_if<std::is_pointer<T0>>(
83         [](auto* vptr) {
84           using TP = mp11::mp_if<std::is_const<std::remove_pointer_t<T0>>, const T*, T*>;
85           auto ptp = variant2::get_if<TP>(vptr);
86           return ptp ? *ptp : nullptr;
87         },
88         [](auto* vptr) { return variant2::get_if<T>(vptr); }, &(v->impl));
89   }
90 
91   template <class T0, class Visitor, class Variant>
visit_implboost::histogram::detail::variant_access92   static decltype(auto) visit_impl(mp11::mp_identity<T0>, Visitor&& vis, Variant&& v) {
93     return variant2::visit(std::forward<Visitor>(vis), v.impl);
94   }
95 
96   template <class T0, class Visitor, class Variant>
visit_implboost::histogram::detail::variant_access97   static decltype(auto) visit_impl(mp11::mp_identity<T0*>, Visitor&& vis, Variant&& v) {
98     return variant2::visit(
99         [&vis](auto&& x) -> decltype(auto) { return std::forward<Visitor>(vis)(*x); },
100         v.impl);
101   }
102 
103   template <class Visitor, class Variant>
visitboost::histogram::detail::variant_access104   static decltype(auto) visit(Visitor&& vis, Variant&& v) {
105     using T0 = mp11::mp_first<std::decay_t<Variant>>;
106     return visit_impl(mp11::mp_identity<T0>{}, std::forward<Visitor>(vis),
107                       std::forward<Variant>(v));
108   }
109 };
110 
111 template <class A>
112 decltype(auto) metadata_impl(A&& a, decltype(a.metadata(), 0)) {
113   return std::forward<A>(a).metadata();
114 }
115 
116 template <class A>
metadata_impl(A &&,float)117 axis::null_type& metadata_impl(A&&, float) {
118   return detail::null_value;
119 }
120 
121 } // namespace detail
122 
123 namespace axis {
124 namespace traits {
125 
126 /** Value type for axis type.
127 
128   Doxygen does not render this well. This is a meta-function (template alias), it accepts
129   an axis type and returns the value type.
130 
131   The value type is deduced from the argument of the `Axis::index` method. Const
132   references are decayed to the their value types, for example, the type deduced for
133   `Axis::index(const int&)` is `int`.
134 
135   The deduction always succeeds if the axis type models the Axis concept correctly. Errors
136   come from violations of the concept, in particular, an index method that is templated or
137   overloaded is not allowed.
138 
139   @tparam Axis axis type.
140 */
141 template <class Axis>
142 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
143 using value_type = typename detail::value_type_deducer<Axis>::type;
144 #else
145 struct value_type;
146 #endif
147 
148 /** Whether axis is continuous or discrete.
149 
150   Doxygen does not render this well. This is a meta-function (template alias), it accepts
151   an axis type and returns a compile-time boolean.
152 
153   If the boolean is true, the axis is continuous (covers a continuous range of values).
154   Otherwise it is discrete (covers discrete values).
155 */
156 template <class Axis>
157 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
158 using is_continuous = typename std::is_floating_point<traits::value_type<Axis>>::type;
159 #else
160 struct is_continuous;
161 #endif
162 
163 /** Meta-function to detect whether an axis is reducible.
164 
165   Doxygen does not render this well. This is a meta-function (template alias), it accepts
166   an axis type and represents compile-time boolean which is true or false, depending on
167   whether the axis can be reduced with boost::histogram::algorithm::reduce().
168 
169   An axis can be made reducible by adding a special constructor, see Axis concept for
170   details.
171 
172   @tparam Axis axis type.
173  */
174 template <class Axis>
175 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
176 using is_reducible = std::is_constructible<Axis, const Axis&, axis::index_type,
177                                            axis::index_type, unsigned>;
178 #else
179 struct is_reducible;
180 #endif
181 
182 /** Get axis options for axis type.
183 
184   Doxygen does not render this well. This is a meta-function (template alias), it accepts
185   an axis type and returns the boost::histogram::axis::option::bitset.
186 
187   If Axis::options() is valid and constexpr, get_options is the corresponding
188   option type. Otherwise, it is boost::histogram::axis::option::growth_t, if the
189   axis has a method `update`, else boost::histogram::axis::option::none_t.
190 
191   @tparam Axis axis type
192 */
193 template <class Axis>
194 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
195 using get_options = decltype(detail::traits_options<Axis>(detail::priority<2>{}));
196 
197 template <class Axis>
198 using static_options [[deprecated("use get_options instead")]] = get_options<Axis>;
199 
200 #else
201 struct get_options;
202 #endif
203 
204 /** Meta-function to detect whether an axis is inclusive.
205 
206   Doxygen does not render this well. This is a meta-function (template alias), it accepts
207   an axis type and represents compile-time boolean which is true or false, depending on
208   whether the axis is inclusive or not.
209 
210   An axis with underflow and overflow bins is always inclusive, but an axis may be
211   inclusive under other conditions. The meta-function checks for the method `constexpr
212   static bool inclusive()`, and uses the result. If this method is not present, it uses
213   get_options<Axis> and checks whether the underflow and overflow bits are present.
214 
215   An inclusive axis has a bin for every possible input value. A histogram which consists
216   only of inclusive axes can be filled more efficiently, since input values always
217   end up in a valid cell and there is no need to keep track of input tuples that need to
218   be discarded.
219 
220   @tparam Axis axis type
221 */
222 template <class Axis>
223 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
224 using is_inclusive = decltype(detail::traits_is_inclusive<Axis>(detail::priority<1>{}));
225 
226 template <class Axis>
227 using static_is_inclusive [[deprecated("use is_inclusive instead")]] = is_inclusive<Axis>;
228 
229 #else
230 struct is_inclusive;
231 #endif
232 
233 /** Meta-function to detect whether an axis is ordered.
234 
235   Doxygen does not render this well. This is a meta-function (template alias), it accepts
236   an axis type and returns a compile-time boolean. If the boolean is true, the axis is
237   ordered.
238 
239   The meta-function checks for the method `constexpr static bool ordered()`, and uses the
240   result. If this method is not present, it returns true if the value type of the Axis is
241   arithmetic and false otherwise.
242 
243   An ordered axis has a value type that is ordered, which means that indices i <
244   j < k implies either value(i) < value(j) < value(k) or value(i) > value(j) > value(k)
245   for all i,j,k. For example, the integer axis is ordered, but the category axis is not.
246   Axis which are not ordered must not have underflow bins, because they only have an
247   "other" category, which is identified with the overflow bin if it is available.
248 
249   @tparam Axis axis type
250 */
251 template <class Axis>
252 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
253 using is_ordered = decltype(detail::traits_is_ordered<Axis>(detail::priority<1>{}));
254 #else
255 struct is_ordered;
256 #endif
257 
258 /** Returns axis options as unsigned integer.
259 
260   See get_options for details.
261 
262   @param axis any axis instance
263 */
264 template <class Axis>
options(const Axis & axis)265 constexpr unsigned options(const Axis& axis) noexcept {
266   (void)axis;
267   return get_options<Axis>::value;
268 }
269 
270 // specialization for variant
271 template <class... Ts>
options(const variant<Ts...> & axis)272 unsigned options(const variant<Ts...>& axis) noexcept {
273   return axis.options();
274 }
275 
276 /** Returns true if axis is inclusive or false.
277 
278   See is_inclusive for details.
279 
280   @param axis any axis instance
281 */
282 template <class Axis>
inclusive(const Axis & axis)283 constexpr bool inclusive(const Axis& axis) noexcept {
284   (void)axis;
285   return is_inclusive<Axis>::value;
286 }
287 
288 // specialization for variant
289 template <class... Ts>
inclusive(const variant<Ts...> & axis)290 bool inclusive(const variant<Ts...>& axis) noexcept {
291   return axis.inclusive();
292 }
293 
294 /** Returns true if axis is ordered or false.
295 
296   See is_ordered for details.
297 
298   @param axis any axis instance
299 */
300 template <class Axis>
ordered(const Axis & axis)301 constexpr bool ordered(const Axis& axis) noexcept {
302   (void)axis;
303   return is_ordered<Axis>::value;
304 }
305 
306 // specialization for variant
307 template <class... Ts>
ordered(const variant<Ts...> & axis)308 bool ordered(const variant<Ts...>& axis) noexcept {
309   return axis.ordered();
310 }
311 
312 /** Returns true if axis is continuous or false.
313 
314   See is_continuous for details.
315 
316   @param axis any axis instance
317 */
318 template <class Axis>
continuous(const Axis & axis)319 constexpr bool continuous(const Axis& axis) noexcept {
320   (void)axis;
321   return is_continuous<Axis>::value;
322 }
323 
324 // specialization for variant
325 template <class... Ts>
continuous(const variant<Ts...> & axis)326 bool continuous(const variant<Ts...>& axis) noexcept {
327   return axis.continuous();
328 }
329 
330 /** Returns axis size plus any extra bins for under- and overflow.
331 
332   @param axis any axis instance
333 */
334 template <class Axis>
extent(const Axis & axis)335 index_type extent(const Axis& axis) noexcept {
336   const auto opt = options(axis);
337   return axis.size() + (opt & option::underflow ? 1 : 0) +
338          (opt & option::overflow ? 1 : 0);
339 }
340 
341 /** Returns reference to metadata of an axis.
342 
343   If the expression x.metadata() for an axis instance `x` (maybe const) is valid, return
344   the result. Otherwise, return a reference to a static instance of
345   boost::histogram::axis::null_type.
346 
347   @param axis any axis instance
348 */
349 template <class Axis>
metadata(Axis && axis)350 decltype(auto) metadata(Axis&& axis) noexcept {
351   return detail::metadata_impl(std::forward<Axis>(axis), 0);
352 }
353 
354 /** Returns axis value for index.
355 
356   If the axis has no `value` method, throw std::runtime_error. If the method exists and
357   accepts a floating point index, pass the index and return the result. If the method
358   exists but accepts only integer indices, cast the floating point index to int, pass this
359   index and return the result.
360 
361   @param axis any axis instance
362   @param index floating point axis index
363 */
364 template <class Axis>
value(const Axis & axis,real_index_type index)365 decltype(auto) value(const Axis& axis, real_index_type index) {
366   return detail::value_method_switch(
367       [index](const auto& a) { return a.value(static_cast<index_type>(index)); },
368       [index](const auto& a) { return a.value(index); }, axis, detail::priority<1>{});
369 }
370 
371 /** Returns axis value for index if it is convertible to target type or throws.
372 
373   Like boost::histogram::axis::traits::value, but converts the result into the requested
374   return type. If the conversion is not possible, throws std::runtime_error.
375 
376   @tparam Result requested return type
377   @tparam Axis axis type
378   @param axis any axis instance
379   @param index floating point axis index
380 */
381 template <class Result, class Axis>
value_as(const Axis & axis,real_index_type index)382 Result value_as(const Axis& axis, real_index_type index) {
383   return detail::try_cast<Result, std::runtime_error>(
384       value(axis, index)); // avoid conversion warning
385 }
386 
387 /** Returns axis index for value.
388 
389   Throws std::invalid_argument if the value argument is not implicitly convertible.
390 
391   @param axis any axis instance
392   @param value argument to be passed to `index` method
393 */
394 template <class Axis, class U>
index(const Axis & axis,const U & value)395 axis::index_type index(const Axis& axis, const U& value) noexcept(
396     std::is_convertible<U, value_type<Axis>>::value) {
397   return axis.index(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
398 }
399 
400 // specialization for variant
401 template <class... Ts, class U>
index(const variant<Ts...> & axis,const U & value)402 axis::index_type index(const variant<Ts...>& axis, const U& value) {
403   return axis.index(value);
404 }
405 
406 /** Return axis rank (how many arguments it processes).
407 
408   @param axis any axis instance
409 */
410 template <class Axis>
rank(const Axis & axis)411 constexpr unsigned rank(const Axis& axis) {
412   (void)axis;
413   using T = value_type<Axis>;
414   // cannot use mp_eval_or since T could be a fixed-sized sequence
415   return mp11::mp_eval_if_not<detail::is_tuple<T>, mp11::mp_size_t<1>, mp11::mp_size,
416                               T>::value;
417 }
418 
419 // specialization for variant
420 template <class... Ts>
rank(const axis::variant<Ts...> & axis)421 unsigned rank(const axis::variant<Ts...>& axis) {
422   return detail::variant_access::visit([](const auto& a) { return rank(a); }, axis);
423 }
424 
425 /** Returns pair of axis index and shift for the value argument.
426 
427   Throws `std::invalid_argument` if the value argument is not implicitly convertible to
428   the argument expected by the `index` method. If the result of
429   boost::histogram::axis::traits::get_options<decltype(axis)> has the growth flag set,
430   call `update` method with the argument and return the result. Otherwise, call `index`
431   and return the pair of the result and a zero shift.
432 
433   @param axis any axis instance
434   @param value argument to be passed to `update` or `index` method
435 */
436 template <class Axis, class U>
update(Axis & axis,const U & value)437 std::pair<index_type, index_type> update(Axis& axis, const U& value) noexcept(
438     std::is_convertible<U, value_type<Axis>>::value) {
439   return detail::static_if_c<get_options<Axis>::test(option::growth)>(
440       [&value](auto& a) {
441         return a.update(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
442       },
443       [&value](auto& a) -> std::pair<index_type, index_type> {
444         return {index(a, value), 0};
445       },
446       axis);
447 }
448 
449 // specialization for variant
450 template <class... Ts, class U>
update(variant<Ts...> & axis,const U & value)451 std::pair<index_type, index_type> update(variant<Ts...>& axis, const U& value) {
452   return visit([&value](auto& a) { return a.update(value); }, axis);
453 }
454 
455 /** Returns bin width at axis index.
456 
457   If the axis has no `value` method, throw std::runtime_error. If the method exists and
458   accepts a floating point index, return the result of `axis.value(index + 1) -
459   axis.value(index)`. If the method exists but accepts only integer indices, return 0.
460 
461   @param axis any axis instance
462   @param index bin index
463  */
464 template <class Axis>
width(const Axis & axis,index_type index)465 decltype(auto) width(const Axis& axis, index_type index) {
466   return detail::value_method_switch(
467       [](const auto&) { return 0; },
468       [index](const auto& a) { return a.value(index + 1) - a.value(index); }, axis,
469       detail::priority<1>{});
470 }
471 
472 /** Returns bin width at axis index.
473 
474   Like boost::histogram::axis::traits::width, but converts the result into the requested
475   return type. If the conversion is not possible, throw std::runtime_error.
476 
477   @param axis any axis instance
478   @param index bin index
479  */
480 template <class Result, class Axis>
width_as(const Axis & axis,index_type index)481 Result width_as(const Axis& axis, index_type index) {
482   return detail::value_method_switch(
483       [](const auto&) { return Result{}; },
484       [index](const auto& a) {
485         return detail::try_cast<Result, std::runtime_error>(a.value(index + 1) -
486                                                             a.value(index));
487       },
488       axis, detail::priority<1>{});
489 }
490 
491 } // namespace traits
492 } // namespace axis
493 } // namespace histogram
494 } // namespace boost
495 
496 #endif
497