• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015-2019 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_VARIANT_HPP
8 #define BOOST_HISTOGRAM_AXIS_VARIANT_HPP
9 
10 #include <boost/core/nvp.hpp>
11 #include <boost/histogram/axis/iterator.hpp>
12 #include <boost/histogram/axis/polymorphic_bin.hpp>
13 #include <boost/histogram/axis/traits.hpp>
14 #include <boost/histogram/detail/relaxed_equal.hpp>
15 #include <boost/histogram/detail/static_if.hpp>
16 #include <boost/histogram/detail/type_name.hpp>
17 #include <boost/histogram/detail/variant_proxy.hpp>
18 #include <boost/mp11/algorithm.hpp> // mp_contains
19 #include <boost/mp11/list.hpp>      // mp_first
20 #include <boost/throw_exception.hpp>
21 #include <boost/variant2/variant.hpp>
22 #include <stdexcept>
23 #include <type_traits>
24 #include <utility>
25 
26 namespace boost {
27 namespace histogram {
28 namespace axis {
29 
30 /// Polymorphic axis type
31 template <class... Ts>
32 class variant : public iterator_mixin<variant<Ts...>> {
33   using impl_type = boost::variant2::variant<Ts...>;
34 
35   template <class T>
36   using is_bounded_type = mp11::mp_contains<variant, std::decay_t<T>>;
37 
38   template <class T>
39   using requires_bounded_type = std::enable_if_t<is_bounded_type<T>::value>;
40 
41   using metadata_type = std::remove_const_t<std::remove_reference_t<decltype(
42       traits::metadata(std::declval<std::remove_pointer_t<mp11::mp_first<variant>>>()))>>;
43 
44 public:
45   // cannot import ctors with using directive, it breaks gcc and msvc
46   variant() = default;
47   variant(const variant&) = default;
48   variant& operator=(const variant&) = default;
49   variant(variant&&) = default;
50   variant& operator=(variant&&) = default;
51 
52   template <class T, class = requires_bounded_type<T>>
variant(T && t)53   variant(T&& t) : impl(std::forward<T>(t)) {}
54 
55   template <class T, class = requires_bounded_type<T>>
operator =(T && t)56   variant& operator=(T&& t) {
57     impl = std::forward<T>(t);
58     return *this;
59   }
60 
61   template <class... Us>
variant(const variant<Us...> & u)62   variant(const variant<Us...>& u) {
63     this->operator=(u);
64   }
65 
66   template <class... Us>
operator =(const variant<Us...> & u)67   variant& operator=(const variant<Us...>& u) {
68     visit(
69         [this](const auto& u) {
70           using U = std::decay_t<decltype(u)>;
71           detail::static_if<is_bounded_type<U>>(
72               [this](const auto& u) { this->operator=(u); },
73               [](const auto&) {
74                 BOOST_THROW_EXCEPTION(std::runtime_error(
75                     detail::type_name<U>() + " is not convertible to a bounded type of " +
76                     detail::type_name<variant>()));
77               },
78               u);
79         },
80         u);
81     return *this;
82   }
83 
84   /// Return size of axis.
size() const85   index_type size() const {
86     return visit([](const auto& a) -> index_type { return a.size(); }, *this);
87   }
88 
89   /// Return options of axis or option::none_t if axis has no options.
options() const90   unsigned options() const {
91     return visit([](const auto& a) { return traits::options(a); }, *this);
92   }
93 
94   /// Returns true if the axis is inclusive or false.
inclusive() const95   bool inclusive() const {
96     return visit([](const auto& a) { return traits::inclusive(a); }, *this);
97   }
98 
99   /// Returns true if the axis is ordered or false.
ordered() const100   bool ordered() const {
101     return visit([](const auto& a) { return traits::ordered(a); }, *this);
102   }
103 
104   /// Returns true if the axis is continuous or false.
continuous() const105   bool continuous() const {
106     return visit([](const auto& a) { return traits::continuous(a); }, *this);
107   }
108 
109   /// Return reference to const metadata or instance of null_type if axis has no
110   /// metadata.
metadata() const111   metadata_type& metadata() const {
112     return visit(
113         [](const auto& a) -> metadata_type& {
114           using M = decltype(traits::metadata(a));
115           return detail::static_if<std::is_same<M, metadata_type&>>(
116               [](const auto& a) -> metadata_type& { return traits::metadata(a); },
117               [](const auto&) -> metadata_type& {
118                 BOOST_THROW_EXCEPTION(std::runtime_error(
119                     "cannot return metadata of type " + detail::type_name<M>() +
120                     " through axis::variant interface which uses type " +
121                     detail::type_name<metadata_type>() +
122                     "; use boost::histogram::axis::get to obtain a reference "
123                     "of this axis type"));
124               },
125               a);
126         },
127         *this);
128   }
129 
130   /// Return reference to metadata or instance of null_type if axis has no
131   /// metadata.
metadata()132   metadata_type& metadata() {
133     return visit(
134         [](auto& a) -> metadata_type& {
135           using M = decltype(traits::metadata(a));
136           return detail::static_if<std::is_same<M, metadata_type&>>(
137               [](auto& a) -> metadata_type& { return traits::metadata(a); },
138               [](auto&) -> metadata_type& {
139                 BOOST_THROW_EXCEPTION(std::runtime_error(
140                     "cannot return metadata of type " + detail::type_name<M>() +
141                     " through axis::variant interface which uses type " +
142                     detail::type_name<metadata_type>() +
143                     "; use boost::histogram::axis::get to obtain a reference "
144                     "of this axis type"));
145               },
146               a);
147         },
148         *this);
149   }
150 
151   /** Return index for value argument.
152 
153     Throws std::invalid_argument if axis has incompatible call signature.
154   */
155   template <class U>
index(const U & u) const156   index_type index(const U& u) const {
157     return visit([&u](const auto& a) { return traits::index(a, u); }, *this);
158   }
159 
160   /** Return value for index argument.
161 
162     Only works for axes with value method that returns something convertible
163     to double and will throw a runtime_error otherwise, see
164     axis::traits::value().
165   */
value(real_index_type idx) const166   double value(real_index_type idx) const {
167     return visit([idx](const auto& a) { return traits::value_as<double>(a, idx); },
168                  *this);
169   }
170 
171   /** Return bin for index argument.
172 
173     Only works for axes with value method that returns something convertible
174     to double and will throw a runtime_error otherwise, see
175     axis::traits::value().
176   */
bin(index_type idx) const177   auto bin(index_type idx) const {
178     return visit(
179         [idx](const auto& a) {
180           return detail::value_method_switch(
181               [idx](const auto& a) { // axis is discrete
182                 const double x = traits::value_as<double>(a, idx);
183                 return polymorphic_bin<double>(x, x);
184               },
185               [idx](const auto& a) { // axis is continuous
186                 const double x1 = traits::value_as<double>(a, idx);
187                 const double x2 = traits::value_as<double>(a, idx + 1);
188                 return polymorphic_bin<double>(x1, x2);
189               },
190               a, detail::priority<1>{});
191         },
192         *this);
193   }
194 
195   template <class Archive>
serialize(Archive & ar,unsigned)196   void serialize(Archive& ar, unsigned /* version */) {
197     detail::variant_proxy<variant> p{*this};
198     ar& make_nvp("variant", p);
199   }
200 
201 private:
202   impl_type impl;
203 
204   friend struct detail::variant_access;
205   friend struct boost::histogram::unsafe_access;
206 };
207 
208 // specialization for empty argument list, useful for meta-programming
209 template <>
210 class variant<> {};
211 
212 /// Apply visitor to variant (reference).
213 template <class Visitor, class... Us>
visit(Visitor && vis,variant<Us...> & var)214 decltype(auto) visit(Visitor&& vis, variant<Us...>& var) {
215   return detail::variant_access::visit(vis, var);
216 }
217 
218 /// Apply visitor to variant (movable reference).
219 template <class Visitor, class... Us>
visit(Visitor && vis,variant<Us...> && var)220 decltype(auto) visit(Visitor&& vis, variant<Us...>&& var) {
221   return detail::variant_access::visit(vis, std::move(var));
222 }
223 
224 /// Apply visitor to variant (const reference).
225 template <class Visitor, class... Us>
visit(Visitor && vis,const variant<Us...> & var)226 decltype(auto) visit(Visitor&& vis, const variant<Us...>& var) {
227   return detail::variant_access::visit(vis, var);
228 }
229 
230 /// Returns pointer to T in variant or null pointer if type does not match.
231 template <class T, class... Us>
get_if(variant<Us...> * v)232 auto get_if(variant<Us...>* v) {
233   return detail::variant_access::template get_if<T>(v);
234 }
235 
236 /// Returns pointer to const T in variant or null pointer if type does not match.
237 template <class T, class... Us>
get_if(const variant<Us...> * v)238 auto get_if(const variant<Us...>* v) {
239   return detail::variant_access::template get_if<T>(v);
240 }
241 
242 /// Return reference to T, throws std::runtime_error if type does not match.
243 template <class T, class... Us>
get(variant<Us...> & v)244 decltype(auto) get(variant<Us...>& v) {
245   auto tp = get_if<T>(&v);
246   if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
247   return *tp;
248 }
249 
250 /// Return movable reference to T, throws unspecified exception if type does not match.
251 template <class T, class... Us>
get(variant<Us...> && v)252 decltype(auto) get(variant<Us...>&& v) {
253   auto tp = get_if<T>(&v);
254   if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
255   return std::move(*tp);
256 }
257 
258 /// Return const reference to T, throws unspecified exception if type does not match.
259 template <class T, class... Us>
get(const variant<Us...> & v)260 decltype(auto) get(const variant<Us...>& v) {
261   auto tp = get_if<T>(&v);
262   if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
263   return *tp;
264 }
265 
266 // pass-through version of visit for generic programming
267 template <class Visitor, class T>
visit(Visitor && vis,T && var)268 decltype(auto) visit(Visitor&& vis, T&& var) {
269   return std::forward<Visitor>(vis)(std::forward<T>(var));
270 }
271 
272 // pass-through version of get for generic programming
273 template <class T, class U>
get(U && u)274 decltype(auto) get(U&& u) {
275   return std::forward<U>(u);
276 }
277 
278 // pass-through version of get_if for generic programming
279 template <class T, class U>
get_if(U * u)280 auto get_if(U* u) {
281   return reinterpret_cast<T*>(std::is_same<T, std::decay_t<U>>::value ? u : nullptr);
282 }
283 
284 // pass-through version of get_if for generic programming
285 template <class T, class U>
get_if(const U * u)286 auto get_if(const U* u) {
287   return reinterpret_cast<const T*>(std::is_same<T, std::decay_t<U>>::value ? u
288                                                                             : nullptr);
289 }
290 
291 /** Compare two variants.
292 
293   Return true if the variants point to the same concrete axis type and the types compare
294   equal. Otherwise return false.
295 */
296 template <class... Us, class... Vs>
operator ==(const variant<Us...> & u,const variant<Vs...> & v)297 bool operator==(const variant<Us...>& u, const variant<Vs...>& v) noexcept {
298   return visit([&](const auto& vi) { return u == vi; }, v);
299 }
300 
301 /** Compare variant with a concrete axis type.
302 
303   Return true if the variant point to the same concrete axis type and the types compare
304   equal. Otherwise return false.
305 */
306 template <class... Us, class T>
operator ==(const variant<Us...> & u,const T & t)307 bool operator==(const variant<Us...>& u, const T& t) noexcept {
308   using V = variant<Us...>;
309   return detail::static_if_c<(mp11::mp_contains<V, T>::value ||
310                               mp11::mp_contains<V, T*>::value ||
311                               mp11::mp_contains<V, const T*>::value)>(
312       [&](const auto& t) {
313         using U = std::decay_t<decltype(t)>;
314         const U* tp = detail::variant_access::template get_if<U>(&u);
315         return tp && detail::relaxed_equal{}(*tp, t);
316       },
317       [&](const auto&) { return false; }, t);
318 }
319 
320 template <class T, class... Us>
operator ==(const T & t,const variant<Us...> & u)321 bool operator==(const T& t, const variant<Us...>& u) noexcept {
322   return u == t;
323 }
324 
325 /// The negation of operator==.
326 template <class... Us, class... Ts>
operator !=(const variant<Us...> & u,const variant<Ts...> & t)327 bool operator!=(const variant<Us...>& u, const variant<Ts...>& t) noexcept {
328   return !(u == t);
329 }
330 
331 /// The negation of operator==.
332 template <class... Us, class T>
operator !=(const variant<Us...> & u,const T & t)333 bool operator!=(const variant<Us...>& u, const T& t) noexcept {
334   return !(u == t);
335 }
336 
337 /// The negation of operator==.
338 template <class T, class... Us>
operator !=(const T & t,const variant<Us...> & u)339 bool operator!=(const T& t, const variant<Us...>& u) noexcept {
340   return u != t;
341 }
342 
343 } // namespace axis
344 } // namespace histogram
345 } // namespace boost
346 
347 #endif
348