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