1 // Copyright 2018 Hans Dembinski
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)
7 #ifndef BOOST_HISTOGRAM_AXIS_TRAITS_HPP
8 #define BOOST_HISTOGRAM_AXIS_TRAITS_HPP
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/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>
32 using get_options_from_method = axis::option::bitset<T::options()>;
35 struct static_options_impl {
36 static_assert(std::is_same<std::decay_t<Axis>, Axis>::value,
37 "support of static_options for qualified types was removed, please use "
38 "static_options<std::decay_t<...>>");
39 using type = mp11::mp_eval_or<
40 mp11::mp_if<has_method_update<Axis>, axis::option::growth_t, axis::option::none_t>,
41 get_options_from_method, Axis>;
45 using get_inclusive_from_method = std::integral_constant<bool, T::inclusive()>;
48 struct static_is_inclusive_impl {
49 using type = mp11::mp_eval_or<decltype(static_options_impl<Axis>::type::test(
50 axis::option::underflow | axis::option::overflow)),
51 get_inclusive_from_method, Axis>;
54 template <class I, class D, class A>
55 double value_method_switch_impl1(std::false_type, I&&, D&&, const A&) {
56 // comma trick to make all compilers happy; some would complain about
57 // unreachable code after the throw, others about a missing return
58 return BOOST_THROW_EXCEPTION(
59 std::runtime_error(type_name<A>() + " has no value method")),
63 template <class I, class D, class A>
64 decltype(auto) value_method_switch_impl1(std::true_type, I&& i, D&& d, const A& a) {
65 using T = arg_type<decltype(&A::value)>;
66 return static_if<std::is_same<T, axis::index_type>>(std::forward<I>(i),
67 std::forward<D>(d), a);
70 template <class I, class D, class A>
71 decltype(auto) value_method_switch(I&& i, D&& d, const A& a) {
72 return value_method_switch_impl1(has_method_value<A>{}, std::forward<I>(i),
73 std::forward<D>(d), a);
76 static axis::null_type null_value;
78 struct variant_access {
79 template <class T, class Variant>
80 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>>(
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;
88 [](auto* vptr) { return variant2::get_if<T>(vptr); }, &(v->impl));
91 template <class T0, class Visitor, class Variant>
92 static decltype(auto) visit_impl(mp11::mp_identity<T0>, Visitor&& vis, Variant&& v) {
93 return variant2::visit(std::forward<Visitor>(vis), v.impl);
96 template <class T0, class Visitor, class Variant>
97 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); },
103 template <class Visitor, class Variant>
104 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));
111 } // namespace detail
116 /** Get value type for axis type.
118 Doxygen does not render this well. This is a meta-function (template alias), it accepts
119 an axis type and returns the value type.
121 template <class Axis>
122 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
124 std::remove_cv_t<std::remove_reference_t<detail::arg_type<decltype(&Axis::index)>>>;
129 /** Whether axis is continuous or discrete.
131 Doxygen does not render this well. This is a meta-function (template alias), it accepts
132 an axis type and returns a compile-time boolean. If the boolean is true, the axis is
133 continuous. Otherwise it is discrete.
135 template <class Axis>
136 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
137 using is_continuous = typename std::is_floating_point<traits::value_type<Axis>>::type;
139 struct is_continuous;
142 /** Meta-function to detect whether an axis is reducible.
144 Doxygen does not render this well. This is a meta-function (template alias), it accepts
145 an axis type and represents compile-time boolean which is true or false, depending on
146 whether the axis can be reduced with boost::histogram::algorithm::reduce().
148 @tparam Axis axis type.
150 template <class Axis>
151 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
152 using is_reducible = std::is_constructible<Axis, const Axis&, axis::index_type,
153 axis::index_type, unsigned>;
158 /** Get static axis options for axis type.
160 Doxygen does not render this well. This is a meta-function (template alias), it accepts
161 an axis type and returns the boost::histogram::axis::option::bitset.
163 If Axis::options() is valid and constexpr, static_options is the corresponding
164 option type. Otherwise, it is boost::histogram::axis::option::growth_t, if the
165 axis has a method `update`, else boost::histogram::axis::option::none_t.
167 @tparam Axis axis type
169 template <class Axis>
170 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
171 using static_options = typename detail::static_options_impl<Axis>::type;
173 struct static_options;
176 /** Meta-function to detect whether an axis is inclusive.
178 Doxygen does not render this well. This is a meta-function (template alias), it accepts
179 an axis type and represents compile-time boolean which is true or false, depending on
180 whether the axis is inclusive or not.
182 An inclusive axis has a bin for every possible input value. A histogram which consists
183 only of inclusive axes can be filled more efficiently, since input values always
184 end up in a valid cell and there is no need to keep track of input tuples that need to
187 An axis with underflow and overflow bins is always inclusive, but an axis may be
188 inclusive under other conditions. The meta-function checks for the method `constexpr
189 static bool inclusive()`, and uses the result. If this method is not present, it uses
190 static_options<Axis> and checks whether the underflow and overflow bits are present.
194 template <class Axis>
195 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
196 using static_is_inclusive = typename detail::static_is_inclusive_impl<Axis>::type;
198 struct static_is_inclusive;
201 /** Returns axis options as unsigned integer.
203 If axis.options() is a valid expression, return the result. Otherwise, return
204 static_options<Axis>::value.
206 @param axis any axis instance
208 template <class Axis>
209 constexpr unsigned options(const Axis& axis) noexcept {
210 boost::ignore_unused(axis);
211 return static_options<Axis>::value;
214 // specialization for variant
215 template <class... Ts>
216 unsigned options(const variant<Ts...>& axis) noexcept {
217 return axis.options();
220 /** Returns true if axis is inclusive or false.
222 See static_is_inclusive for details.
224 @param axis any axis instance
226 template <class Axis>
227 constexpr bool inclusive(const Axis& axis) noexcept {
228 boost::ignore_unused(axis);
229 return static_is_inclusive<Axis>::value;
232 // specialization for variant
233 template <class... Ts>
234 bool inclusive(const variant<Ts...>& axis) noexcept {
235 return axis.inclusive();
238 /** Returns axis size plus any extra bins for under- and overflow.
240 @param axis any axis instance
242 template <class Axis>
243 index_type extent(const Axis& axis) noexcept {
244 const auto opt = options(axis);
245 return axis.size() + (opt & option::underflow ? 1 : 0) +
246 (opt & option::overflow ? 1 : 0);
249 /** Returns reference to metadata of an axis.
251 If the expression x.metadata() for an axis instance `x` (maybe const) is valid, return
252 the result. Otherwise, return a reference to a static instance of
253 boost::histogram::axis::null_type.
255 @param axis any axis instance
257 template <class Axis>
258 decltype(auto) metadata(Axis&& axis) noexcept {
259 return detail::static_if<detail::has_method_metadata<std::decay_t<Axis>>>(
260 [](auto&& a) -> decltype(auto) { return a.metadata(); },
261 [](auto &&) -> mp11::mp_if<std::is_const<std::remove_reference_t<Axis>>,
262 axis::null_type const&, axis::null_type&> {
263 return detail::null_value;
265 std::forward<Axis>(axis));
268 /** Returns axis value for index.
270 If the axis has no `value` method, throw std::runtime_error. If the method exists and
271 accepts a floating point index, pass the index and return the result. If the method
272 exists but accepts only integer indices, cast the floating point index to int, pass this
273 index and return the result.
275 @param axis any axis instance
276 @param index floating point axis index
278 template <class Axis>
279 decltype(auto) value(const Axis& axis, real_index_type index) {
280 return detail::value_method_switch(
281 [index](const auto& a) { return a.value(static_cast<index_type>(index)); },
282 [index](const auto& a) { return a.value(index); }, axis);
285 /** Returns axis value for index if it is convertible to target type or throws.
287 Like boost::histogram::axis::traits::value, but converts the result into the requested
288 return type. If the conversion is not possible, throws std::runtime_error.
290 @tparam Result requested return type
291 @tparam Axis axis type
292 @param axis any axis instance
293 @param index floating point axis index
295 template <class Result, class Axis>
296 Result value_as(const Axis& axis, real_index_type index) {
297 return detail::try_cast<Result, std::runtime_error>(
298 value(axis, index)); // avoid conversion warning
301 /** Returns axis index for value.
303 Throws std::invalid_argument if the value argument is not implicitly convertible.
305 @param axis any axis instance
306 @param value argument to be passed to `index` method
308 template <class Axis, class U>
309 axis::index_type index(const Axis& axis, const U& value) noexcept(
310 std::is_convertible<U, value_type<Axis>>::value) {
311 return axis.index(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
314 // specialization for variant
315 template <class... Ts, class U>
316 axis::index_type index(const variant<Ts...>& axis, const U& value) {
317 return axis.index(value);
320 /** Return axis rank (how many arguments it processes).
322 @param axis any axis instance
324 template <class Axis>
325 constexpr unsigned rank(const Axis& axis) {
326 boost::ignore_unused(axis);
327 using T = value_type<Axis>;
328 // cannot use mp_eval_or since T could be a fixed-sized sequence
329 return mp11::mp_eval_if_not<detail::is_tuple<T>, mp11::mp_size_t<1>, mp11::mp_size,
333 // specialization for variant
334 template <class... Ts>
335 unsigned rank(const axis::variant<Ts...>& axis) {
336 return detail::variant_access::visit([](const auto& a) { return rank(a); }, axis);
339 /** Returns pair of axis index and shift for the value argument.
341 Throws `std::invalid_argument` if the value argument is not implicitly convertible to
342 the argument expected by the `index` method. If the result of
343 boost::histogram::axis::traits::static_options<decltype(axis)> has the growth flag set,
344 call `update` method with the argument and return the result. Otherwise, call `index`
345 and return the pair of the result and a zero shift.
347 @param axis any axis instance
348 @param value argument to be passed to `update` or `index` method
350 template <class Axis, class U>
351 std::pair<index_type, index_type> update(Axis& axis, const U& value) noexcept(
352 std::is_convertible<U, value_type<Axis>>::value) {
353 return detail::static_if_c<static_options<Axis>::test(option::growth)>(
355 return a.update(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
357 [&value](auto& a) { return std::make_pair(index(a, value), index_type{0}); }, axis);
360 // specialization for variant
361 template <class... Ts, class U>
362 std::pair<index_type, index_type> update(variant<Ts...>& axis, const U& value) {
363 return visit([&value](auto& a) { return a.update(value); }, axis);
366 /** Returns bin width at axis index.
368 If the axis has no `value` method, throw std::runtime_error. If the method exists and
369 accepts a floating point index, return the result of `axis.value(index + 1) -
370 axis.value(index)`. If the method exists but accepts only integer indices, return 0.
372 @param axis any axis instance
373 @param index bin index
375 template <class Axis>
376 decltype(auto) width(const Axis& axis, index_type index) {
377 return detail::value_method_switch(
378 [](const auto&) { return 0; },
379 [index](const auto& a) { return a.value(index + 1) - a.value(index); }, axis);
382 /** Returns bin width at axis index.
384 Like boost::histogram::axis::traits::width, but converts the result into the requested
385 return type. If the conversion is not possible, throw std::runtime_error.
387 @param axis any axis instance
388 @param index bin index
390 template <class Result, class Axis>
391 Result width_as(const Axis& axis, index_type index) {
392 return detail::value_method_switch(
393 [](const auto&) { return Result{}; },
394 [index](const auto& a) {
395 return detail::try_cast<Result, std::runtime_error>(a.value(index + 1) -
401 } // namespace traits
403 } // namespace histogram