update boost

This commit is contained in:
2023-11-24 12:56:13 -06:00
parent cfc99971af
commit 19d727037a
9260 changed files with 849256 additions and 299957 deletions

View File

@@ -0,0 +1,102 @@
// Copyright Hans Dembinski 2020
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_BOOLEAN_HPP
#define BOOST_HISTOGRAM_AXIS_BOOLEAN_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/fwd.hpp>
#include <string>
namespace boost {
namespace histogram {
namespace axis {
/**
Discrete axis for boolean data.
Binning is a pass-though operation with zero cost, making this the
fastest possible axis. The axis has no internal state apart from the
optional metadata state. The axis has no under- and overflow bins.
It cannot grow and cannot be reduced.
@tparam MetaData type to store meta data.
*/
template <class MetaData>
class boolean : public iterator_mixin<boolean<MetaData>>,
public metadata_base_t<MetaData> {
using value_type = bool;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
public:
/** Construct a boolean axis.
@param meta description of the axis.
The constructor is nothrow if meta is nothrow move constructible.
*/
explicit boolean(metadata_type meta = {}) noexcept(
std::is_nothrow_move_constructible<metadata_type>::value)
: metadata_base(std::move(meta)) {}
/// Return index for value argument.
index_type index(value_type x) const noexcept { return static_cast<index_type>(x); }
/// Return value for index argument.
value_type value(index_type i) const noexcept { return static_cast<value_type>(i); }
/// Return bin for index argument.
value_type bin(index_type i) const noexcept { return value(i); }
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return 2; }
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept { return true; }
/// Returns the options.
static constexpr unsigned options() noexcept { return option::none_t::value; }
template <class M>
bool operator==(const boolean<M>& o) const noexcept {
return detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class M>
bool operator!=(const boolean<M>& o) const noexcept {
return !operator==(o);
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("meta", this->metadata());
}
private:
template <class M>
friend class boolean;
};
#if __cpp_deduction_guides >= 201606
boolean()->boolean<null_type>;
template <class M>
boolean(M) -> boolean<detail::replace_type<std::decay_t<M>, const char*, std::string>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_AXIS_BOOLEAN_HPP

View File

@@ -8,13 +8,16 @@
#define BOOST_HISTOGRAM_AXIS_CATEGORY_HPP
#include <algorithm>
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/compressed_pair.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
@@ -23,8 +26,7 @@ namespace boost {
namespace histogram {
namespace axis {
/**
Maps at a set of unique values to bin indices.
/** Maps at a set of unique values to bin indices.
The axis maps a set of values to bins, following the order of arguments in the
constructor. The optional overflow bin for this axis counts input values that
@@ -38,111 +40,159 @@ namespace axis {
The options `underflow` and `circular` are not allowed. The options `growth`
and `overflow` are mutually exclusive.
*/
*/
template <class Value, class MetaData, class Options, class Allocator>
class category : public iterator_mixin<category<Value, MetaData, Options, Allocator>> {
class category : public iterator_mixin<category<Value, MetaData, Options, Allocator>>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using metadata_type = detail::replace_default<MetaData, std::string>;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type = detail::replace_default<Options, option::overflow_t>;
static_assert(!options_type::test(option::underflow),
"category axis cannot have underflow");
static_assert(!options_type::test(option::circular),
"category axis cannot be circular");
static_assert(!options_type::test(option::growth) ||
!options_type::test(option::overflow),
"growing category axis cannot have overflow");
using allocator_type = Allocator;
using vector_type = std::vector<value_type, allocator_type>;
public:
explicit category(allocator_type alloc = {}) : vec_meta_(vector_type(alloc)) {}
constexpr category() = default;
explicit category(allocator_type alloc) : vec_(alloc) {}
/** Construct from iterator range of unique values.
*
* \param begin begin of category range of unique values.
* \param end end of category range of unique values.
* \param meta description of the axis.
* \param alloc allocator instance to use.
/** Construct from forward iterator range of unique values.
@param begin begin of category range of unique values.
@param end end of category range of unique values.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
The constructor throws `std::invalid_argument` if iterator range is invalid. If the
range contains duplicated values, the behavior of the axis is undefined.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
template <class It, class = detail::requires_iterator<It>>
category(It begin, It end, metadata_type meta = {}, allocator_type alloc = {})
: vec_meta_(vector_type(begin, end, alloc), std::move(meta)) {
if (size() == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
category(It begin, It end, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: metadata_base(std::move(meta)), vec_(alloc) {
// static_asserts were moved here from class scope to satisfy deduction in gcc>=11
static_assert(!options.test(option::underflow),
"category axis cannot have underflow");
static_assert(!options.test(option::circular), "category axis cannot be circular");
static_assert(!(options.test(option::growth) && options.test(option::overflow)),
"growing category axis cannot have entries in overflow bin");
if (std::distance(begin, end) < 0)
BOOST_THROW_EXCEPTION(
std::invalid_argument("end must be reachable by incrementing begin"));
vec_.reserve(std::distance(begin, end));
while (begin != end) vec_.emplace_back(*begin++);
}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class It, class A, class = detail::requires_iterator<It>,
class = detail::requires_allocator<A>>
category(It begin, It end, metadata_type meta, A alloc)
: category(begin, end, std::move(meta), {}, std::move(alloc)) {}
/** Construct axis from iterable sequence of unique values.
*
* \param iterable sequence of unique values.
* \param meta description of the axis.
* \param alloc allocator instance to use.
@param iterable sequence of unique values.
@param meta description of the axis.
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use.
*/
template <class C, class = detail::requires_iterable<C>>
category(const C& iterable, metadata_type meta = {}, allocator_type alloc = {})
: category(std::begin(iterable), std::end(iterable), std::move(meta),
category(const C& iterable, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: category(std::begin(iterable), std::end(iterable), std::move(meta), options,
std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class C, class A, class = detail::requires_iterable<C>,
class = detail::requires_allocator<A>>
category(const C& iterable, metadata_type meta, A alloc)
: category(std::begin(iterable), std::end(iterable), std::move(meta), {},
std::move(alloc)) {}
/** Construct axis from an initializer list of unique values.
*
* \param list `std::initializer_list` of unique values.
* \param meta description of the axis.
* \param alloc allocator instance to use.
@param list `std::initializer_list` of unique values.
@param meta description of the axis.
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use.
*/
template <class U>
category(std::initializer_list<U> list, metadata_type meta = {},
allocator_type alloc = {})
: category(list.begin(), list.end(), std::move(meta), std::move(alloc)) {}
options_type options = {}, allocator_type alloc = {})
: category(list.begin(), list.end(), std::move(meta), options, std::move(alloc)) {}
/// Constructor used by algorithm::reduce to shrink and rebin.
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class U, class A, class = detail::requires_allocator<A>>
category(std::initializer_list<U> list, metadata_type meta, A alloc)
: category(list.begin(), list.end(), std::move(meta), {}, std::move(alloc)) {}
/// Constructor used by algorithm::reduce to shrink and rebin (not for users).
category(const category& src, index_type begin, index_type end, unsigned merge)
: category(src.vec_meta_.first().begin() + begin,
src.vec_meta_.first().begin() + end, src.metadata()) {
// LCOV_EXCL_START: gcc-8 is missing the delegated ctor for no reason
: category(src.vec_.begin() + begin, src.vec_.begin() + end, src.metadata(), {},
src.get_allocator())
// LCOV_EXCL_STOP
{
if (merge > 1)
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot merge bins for category axis"));
}
/// Return index for value argument.
index_type index(const value_type& x) const noexcept {
const auto beg = vec_meta_.first().begin();
const auto end = vec_meta_.first().end();
const auto beg = vec_.begin();
const auto end = vec_.end();
return static_cast<index_type>(std::distance(beg, std::find(beg, end, x)));
}
/// Returns index and shift (if axis has grown) for the passed argument.
auto update(const value_type& x) {
std::pair<index_type, index_type> update(const value_type& x) {
const auto i = index(x);
if (i < size()) return std::make_pair(i, 0);
vec_meta_.first().emplace_back(x);
return std::make_pair(i, -1);
if (i < size()) return {i, 0};
vec_.emplace_back(x);
return {i, -1};
}
/// Return value for index argument.
/// Throws `std::out_of_range` if the index is out of bounds.
decltype(auto) value(index_type idx) const {
auto value(index_type idx) const
-> std::conditional_t<std::is_scalar<value_type>::value, value_type,
const value_type&> {
if (idx < 0 || idx >= size())
BOOST_THROW_EXCEPTION(std::out_of_range("category index out of range"));
return vec_meta_.first()[idx];
return vec_[idx];
}
/// Return value for index argument.
decltype(auto) bin(index_type idx) const noexcept { return value(idx); }
/// Return value for index argument; alias for value(...).
decltype(auto) bin(index_type idx) const { return value(idx); }
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept {
return static_cast<index_type>(vec_meta_.first().size());
}
index_type size() const noexcept { return static_cast<index_type>(vec_.size()); }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
/// Returns reference to metadata.
metadata_type& metadata() noexcept { return vec_meta_.second(); }
/// Returns reference to const metadata.
const metadata_type& metadata() const noexcept { return vec_meta_.second(); }
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept {
return options() & (option::overflow | option::growth);
}
/// Indicate that the axis is not ordered.
static constexpr bool ordered() noexcept { return false; }
template <class V, class M, class O, class A>
bool operator==(const category<V, M, O, A>& o) const noexcept {
const auto& a = vec_meta_.first();
const auto& b = o.vec_meta_.first();
return std::equal(a.begin(), a.end(), b.begin(), b.end()) &&
detail::relaxed_equal(metadata(), o.metadata());
const auto& a = vec_;
const auto& b = o.vec_;
return std::equal(a.begin(), a.end(), b.begin(), b.end(), detail::relaxed_equal{}) &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class M, class O, class A>
@@ -150,13 +200,16 @@ public:
return !operator==(o);
}
auto get_allocator() const { return vec_meta_.first().get_allocator(); }
allocator_type get_allocator() const { return vec_.get_allocator(); }
template <class Archive>
void serialize(Archive&, unsigned);
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("seq", vec_);
ar& make_nvp("meta", this->metadata());
}
private:
detail::compressed_pair<vector_type, metadata_type> vec_meta_;
vector_type vec_;
template <class V, class M, class O, class A>
friend class category;
@@ -165,15 +218,18 @@ private:
#if __cpp_deduction_guides >= 201606
template <class T>
category(std::initializer_list<T>)->category<T>;
category(std::initializer_list<const char*>)->category<std::string>;
template <class T>
category(std::initializer_list<T>, const char*)->category<T>;
category(std::initializer_list<T>)
-> category<detail::replace_cstring<std::decay_t<T>>, null_type>;
template <class T, class M>
category(std::initializer_list<T>, const M&)->category<T, M>;
category(std::initializer_list<T>, M)
-> category<detail::replace_cstring<std::decay_t<T>>,
detail::replace_cstring<std::decay_t<M>>>;
template <class T, class M, unsigned B>
category(std::initializer_list<T>, M, const option::bitset<B>&)
-> category<detail::replace_cstring<std::decay_t<T>>,
detail::replace_cstring<std::decay_t<M>>, option::bitset<B>>;
#endif

View File

@@ -7,15 +7,21 @@
#ifndef BOOST_HISTOGRAM_AXIS_INTEGER_HPP
#define BOOST_HISTOGRAM_AXIS_INTEGER_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/compressed_pair.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/histogram/detail/convert_integer.hpp>
#include <boost/histogram/detail/limits.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
@@ -23,45 +29,68 @@ namespace boost {
namespace histogram {
namespace axis {
/**
Axis for an interval of integer values with unit steps.
/** Axis for an interval of integer values with unit steps.
Binning is a O(1) operation. This axis bins faster than a regular axis.
Binning is a O(1) operation. This axis bins even faster than a regular axis.
@tparam Value input value type. Must be integer or floating point.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option (all values allowed).
The options `growth` and `circular` are mutually exclusive. If the axis uses
integers and either `growth` or `circular` are set, the axis cannot have
the options `underflow` or `overflow` set.
@tparam Value input value type. Must be integer or floating point.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
*/
template <class Value, class MetaData, class Options>
class integer : public iterator_mixin<integer<Value, MetaData, Options>> {
static_assert(std::is_integral<Value>::value || std::is_floating_point<Value>::value,
"integer axis requires type floating point or integral type");
class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using local_index_type = std::conditional_t<std::is_integral<value_type>::value,
index_type, real_index_type>;
using metadata_type = detail::replace_default<MetaData, std::string>;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type =
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
static_assert(!options_type::test(option::circular) ||
std::is_floating_point<value_type>::value ||
!options_type::test(option::overflow),
"integer axis with integral type cannot have overflow");
using local_index_type = std::conditional_t<std::is_integral<value_type>::value,
index_type, real_index_type>;
public:
constexpr integer() = default;
/** Construct over semi-open integer interval [start, stop).
*
* \param start first integer of covered range.
* \param stop one past last integer of covered range.
* \param meta description of the axis.
@param start first integer of covered range.
@param stop one past last integer of covered range.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The constructor throws `std::invalid_argument` if start is not less than stop.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
integer(value_type start, value_type stop, metadata_type meta = {})
: size_meta_(static_cast<index_type>(stop - start), std::move(meta)), min_(start) {
if (stop <= start) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
integer(value_type start, value_type stop, metadata_type meta = {},
options_type options = {})
: metadata_base(std::move(meta))
, size_(static_cast<index_type>(stop - start))
, min_(start) {
static_assert(
std::is_integral<value_type>::value || std::is_floating_point<value_type>::value,
"integer axis requires floating point or integral type");
static_assert(!(options.test(option::circular) && options.test(option::growth)),
"circular and growth options are mutually exclusive");
static_assert(
std::is_floating_point<value_type>::value ||
!((options.test(option::growth) || options.test(option::circular)) &&
(options.test(option::overflow) || options.test(option::underflow))),
"circular or growing integer axis with integral type "
"cannot have entries in underflow or overflow bins");
if (!(stop >= start)) // double negation so it works with NaN
BOOST_THROW_EXCEPTION(std::invalid_argument("stop >= start required"));
}
/// Constructor used by algorithm::reduce to shrink and rebin.
@@ -75,66 +104,76 @@ public:
/// Return index for value argument.
index_type index(value_type x) const noexcept {
return index_impl(std::is_floating_point<value_type>(), x);
return index_impl(options_type::test(axis::option::circular),
std::is_floating_point<value_type>{},
static_cast<double>(x - min_));
}
/// Returns index and shift (if axis has grown) for the passed argument.
auto update(value_type x) noexcept {
auto impl = [this](long x) {
auto impl = [this](long x) -> std::pair<index_type, index_type> {
const auto i = x - min_;
if (i >= 0) {
const auto k = static_cast<axis::index_type>(i);
if (k < size()) return std::make_pair(k, 0);
if (k < size()) return {k, 0};
const auto n = k - size() + 1;
size_meta_.first() += n;
return std::make_pair(k, -n);
size_ += n;
return {k, -n};
}
const auto k = static_cast<axis::index_type>(
detail::static_if<std::is_floating_point<value_type>>(
[](auto x) { return std::floor(x); }, [](auto x) { return x; }, i));
min_ += k;
size_meta_.first() -= k;
return std::make_pair(0, -k);
size_ -= k;
return {0, -k};
};
return detail::static_if<std::is_floating_point<value_type>>(
[this, impl](auto x) {
[this, impl](auto x) -> std::pair<index_type, index_type> {
if (std::isfinite(x)) return impl(static_cast<long>(std::floor(x)));
// this->size() is workaround for gcc-5 bug
return std::make_pair(x < 0 ? -1 : this->size(), 0);
return {x < 0 ? -1 : this->size(), 0};
},
impl, x);
}
/// Return value for index argument.
value_type value(local_index_type i) const noexcept {
if (!options_type::test(option::circular)) {
if (!options_type::test(option::circular) &&
std::is_floating_point<value_type>::value) {
if (i < 0) return detail::lowest<value_type>();
if (i > size()) { return detail::highest<value_type>(); }
if (i > size()) return detail::highest<value_type>();
}
return min_ + i;
}
/// Return bin for index argument.
decltype(auto) bin(index_type idx) const noexcept {
return detail::static_if<std::is_floating_point<local_index_type>>(
return detail::static_if<std::is_floating_point<value_type>>(
[this](auto idx) { return interval_view<integer>(*this, idx); },
[this](auto idx) { return this->value(idx); }, idx);
}
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return size_meta_.first(); }
index_type size() const noexcept { return size_; }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
/// Returns reference to metadata.
metadata_type& metadata() noexcept { return size_meta_.second(); }
/// Returns reference to const metadata.
const metadata_type& metadata() const noexcept { return size_meta_.second(); }
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept {
// If axis has underflow and overflow, it is inclusive.
// If axis is growing or circular:
// - it is inclusive if value_type is an integer.
// - it is not inclusive if value_type is floating point, because of nan and inf.
constexpr bool full_flow = options_type().test(option::underflow | option::overflow);
return full_flow || (std::is_integral<value_type>::value &&
(options() & (option::growth | option::circular)));
}
template <class V, class M, class O>
bool operator==(const integer<V, M, O>& o) const noexcept {
return size() == o.size() && detail::relaxed_equal(metadata(), o.metadata()) &&
min_ == o.min_;
return size() == o.size() && min_ == o.min_ &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class M, class O>
@@ -143,31 +182,32 @@ public:
}
template <class Archive>
void serialize(Archive&, unsigned);
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("size", size_);
ar& make_nvp("meta", this->metadata());
ar& make_nvp("min", min_);
}
private:
index_type index_impl(std::false_type, int x) const noexcept {
const auto z = x - min_;
if (options_type::test(option::circular))
return static_cast<index_type>(z - std::floor(float(z) / size()) * size());
if (z < size()) return z >= 0 ? z : -1;
// axis not circular
template <class B>
index_type index_impl(std::false_type, B, double z) const noexcept {
if (z < size()) return z >= 0 ? static_cast<index_type>(z) : -1;
return size();
}
template <typename T>
index_type index_impl(std::true_type, T x) const noexcept {
// need to handle NaN, cannot simply cast to int and call int-implementation
const auto z = x - min_;
if (options_type::test(option::circular)) {
if (std::isfinite(z))
return static_cast<index_type>(std::floor(z) - std::floor(z / size()) * size());
} else if (z < size()) {
return z >= 0 ? static_cast<index_type>(z) : -1;
}
return size();
// value_type is integer, axis circular
index_type index_impl(std::true_type, std::false_type, double z) const noexcept {
return static_cast<index_type>(z - std::floor(z / size()) * size());
}
detail::compressed_pair<index_type, metadata_type> size_meta_{0};
// value_type is floating point, must handle +/-infinite or nan, axis circular
index_type index_impl(std::true_type, std::true_type, double z) const noexcept {
if (std::isfinite(z)) return index_impl(std::true_type{}, std::false_type{}, z);
return z < size() ? -1 : size();
}
index_type size_{0};
value_type min_{0};
template <class V, class M, class O>
@@ -177,13 +217,18 @@ private:
#if __cpp_deduction_guides >= 201606
template <class T>
integer(T, T)->integer<detail::convert_integer<T, index_type>>;
template <class T>
integer(T, T, const char*)->integer<detail::convert_integer<T, index_type>>;
integer(T, T) -> integer<detail::convert_integer<T, index_type>, null_type>;
template <class T, class M>
integer(T, T, M)->integer<detail::convert_integer<T, index_type>, M>;
integer(T, T, M)
-> integer<detail::convert_integer<T, index_type>,
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
template <class T, class M, unsigned B>
integer(T, T, M, const option::bitset<B>&)
-> integer<detail::convert_integer<T, index_type>,
detail::replace_type<std::decay_t<M>, const char*, std::string>,
option::bitset<B>>;
#endif

View File

@@ -7,6 +7,8 @@
#ifndef BOOST_HISTOGRAM_AXIS_INTERVAL_VIEW_HPP
#define BOOST_HISTOGRAM_AXIS_INTERVAL_VIEW_HPP
#include <boost/histogram/fwd.hpp>
namespace boost {
namespace histogram {
namespace axis {
@@ -16,12 +18,12 @@ namespace axis {
Represents the current bin interval.
*/
template <typename Axis>
template <class Axis>
class interval_view {
public:
interval_view(const Axis& axis, int idx) : axis_(axis), idx_(idx) {}
interval_view(const Axis& axis, index_type idx) : axis_(axis), idx_(idx) {}
// avoid viewing a temporary that goes out of scope
interval_view(Axis&& axis, int idx) = delete;
interval_view(Axis&& axis, index_type idx) = delete;
/// Return lower edge of bin.
decltype(auto) lower() const noexcept { return axis_.value(idx_); }
@@ -32,19 +34,19 @@ public:
/// Return width of bin.
decltype(auto) width() const noexcept { return upper() - lower(); }
template <typename BinType>
template <class BinType>
bool operator==(const BinType& rhs) const noexcept {
return lower() == rhs.lower() && upper() == rhs.upper();
}
template <typename BinType>
template <class BinType>
bool operator!=(const BinType& rhs) const noexcept {
return !operator==(rhs);
}
private:
const Axis& axis_;
const int idx_;
const index_type idx_;
};
} // namespace axis

View File

@@ -8,42 +8,36 @@
#define BOOST_HISTOGRAM_AXIS_ITERATOR_HPP
#include <boost/histogram/axis/interval_view.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/iterator/iterator_adaptor.hpp>
#include <boost/iterator/reverse_iterator.hpp>
#include <boost/histogram/detail/iterator_adaptor.hpp>
#include <iterator>
namespace boost {
namespace histogram {
namespace axis {
template <typename Axis>
class iterator
: public boost::iterator_adaptor<iterator<Axis>, int,
decltype(std::declval<const Axis&>().bin(0)),
std::random_access_iterator_tag,
decltype(std::declval<const Axis&>().bin(0)), int> {
template <class Axis>
class iterator : public detail::iterator_adaptor<iterator<Axis>, index_type,
decltype(std::declval<Axis>().bin(0))> {
public:
explicit iterator(const Axis& axis, int idx)
using reference = typename iterator::iterator_adaptor_::reference;
/// Make iterator from axis and index.
iterator(const Axis& axis, index_type idx)
: iterator::iterator_adaptor_(idx), axis_(axis) {}
protected:
bool equal(const iterator& other) const noexcept {
return &axis_ == &other.axis_ && this->base() == other.base();
}
decltype(auto) dereference() const { return axis_.bin(this->base()); }
/// Return current bin object.
reference operator*() const { return axis_.bin(this->base()); }
private:
const Axis& axis_;
friend class boost::iterator_core_access;
};
/// Uses CRTP to inject iterator logic into Derived.
template <typename Derived>
template <class Derived>
class iterator_mixin {
public:
using const_iterator = iterator<Derived>;
using const_reverse_iterator = boost::reverse_iterator<const_iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/// Bin iterator to beginning of the axis (read-only).
const_iterator begin() const noexcept {
@@ -58,12 +52,12 @@ public:
/// Reverse bin iterator to the last entry of the axis (read-only).
const_reverse_iterator rbegin() const noexcept {
return boost::make_reverse_iterator(end());
return std::make_reverse_iterator(end());
}
/// Reverse bin iterator to the end (read-only).
const_reverse_iterator rend() const noexcept {
return boost::make_reverse_iterator(begin());
return std::make_reverse_iterator(begin());
}
};

View File

@@ -0,0 +1,102 @@
// Copyright 2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_METADATA_BASE_HPP
#define BOOST_HISTOGRAM_AXIS_METADATA_BASE_HPP
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <string>
#include <type_traits>
namespace boost {
namespace histogram {
namespace axis {
/** Meta data holder with space optimization for empty meta data types.
Allows write-access to metadata even if const.
@tparam Metadata Wrapped meta data type.
*/
template <class Metadata, bool Detail>
class metadata_base {
protected:
using metadata_type = Metadata;
static_assert(std::is_default_constructible<metadata_type>::value,
"metadata must be default constructible");
static_assert(std::is_copy_constructible<metadata_type>::value,
"metadata must be copy constructible");
static_assert(std::is_copy_assignable<metadata_type>::value,
"metadata must be copy assignable");
// std::string explicitly guarantees nothrow only in C++17
static_assert(std::is_same<metadata_type, std::string>::value ||
std::is_nothrow_move_constructible<metadata_type>::value,
"metadata must be nothrow move constructible");
metadata_base() = default;
metadata_base(const metadata_base&) = default;
metadata_base& operator=(const metadata_base&) = default;
// make noexcept because std::string is nothrow move constructible only in C++17
metadata_base(metadata_base&& o) noexcept : data_(std::move(o.data_)) {}
metadata_base(metadata_type&& o) noexcept : data_(std::move(o)) {}
// make noexcept because std::string is nothrow move constructible only in C++17
metadata_base& operator=(metadata_base&& o) noexcept {
data_ = std::move(o.data_);
return *this;
}
public:
/// Returns reference to metadata.
metadata_type& metadata() noexcept { return data_; }
/// Returns reference to mutable metadata from const axis.
metadata_type& metadata() const noexcept { return data_; }
private:
mutable metadata_type data_;
};
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
// specialization for empty metadata
template <class Metadata>
class metadata_base<Metadata, true> {
protected:
using metadata_type = Metadata;
metadata_base() = default;
metadata_base(metadata_type&&) {}
metadata_base& operator=(metadata_type&&) { return *this; }
public:
metadata_type& metadata() noexcept {
return static_cast<const metadata_base&>(*this).metadata();
}
metadata_type& metadata() const noexcept {
static metadata_type data;
return data;
}
};
template <class Metadata, class Detail = detail::replace_default<Metadata, std::string>>
using metadata_base_t =
metadata_base<Detail, (std::is_empty<Detail>::value && std::is_final<Detail>::value)>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif

View File

@@ -7,7 +7,6 @@
#ifndef BOOST_HISTOGRAM_AXIS_OPTION_HPP
#define BOOST_HISTOGRAM_AXIS_OPTION_HPP
#include <boost/mp11.hpp>
#include <type_traits>
/**
@@ -25,10 +24,12 @@ namespace option {
/// Holder of axis options.
template <unsigned Bits>
struct bitset : std::integral_constant<unsigned, Bits> {
/// Returns true if all option flags in the argument are set and false otherwise.
template <unsigned B>
static constexpr auto test(bitset<B>) {
return std::integral_constant<bool, static_cast<bool>(Bits & B)>{};
// B + 0 needed to avoid false positive -Wtautological-compare in gcc-6
return std::integral_constant<bool, static_cast<bool>((Bits & B) == (B + 0))>{};
}
};
@@ -56,23 +57,28 @@ constexpr auto operator-(bitset<B1>, bitset<B2>) {
@tparam Pos position of the bit in the set.
*/
template <unsigned Pos>
struct bit : bitset<(1 << Pos)> {};
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using bit = bitset<(1 << Pos)>;
#else
struct bit;
#endif
/// All options off.
using none_t = bitset<0>;
constexpr none_t none{}; ///< Instance of `none_t`.
/// Axis has an underflow bin. Mutually exclusive with `circular`.
using underflow_t = bit<0>;
constexpr underflow_t underflow{}; ///< Instance of `underflow_t`.
/// Axis has overflow bin.
using overflow_t = bit<1>;
constexpr overflow_t overflow{}; ///< Instance of `overflow_t`.
/// Axis is circular. Mutually exclusive with `growth` and `underflow`.
using circular_t = bit<2>;
constexpr circular_t circular{}; ///< Instance of `circular_t`.
/// Axis can grow. Mutually exclusive with `circular`.
using growth_t = bit<3>;
constexpr growth_t growth{}; ///< Instance of `growth_t`.
constexpr none_t none{}; ///< Instance of `none_t`.
constexpr underflow_t underflow{}; ///< Instance of `underflow_t`.
constexpr overflow_t overflow{}; ///< Instance of `overflow_t`.
constexpr circular_t circular{}; ///< Instance of `circular_t`.
constexpr growth_t growth{}; ///< Instance of `growth_t`.
} // namespace option
} // namespace axis

View File

@@ -9,47 +9,80 @@
#ifndef BOOST_HISTOGRAM_AXIS_OSTREAM_HPP
#define BOOST_HISTOGRAM_AXIS_OSTREAM_HPP
#include <boost/assert.hpp>
#include <boost/core/typeinfo.hpp>
#include <boost/histogram/axis/regular.hpp>
#include <boost/histogram/detail/cat.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/histogram/detail/counting_streambuf.hpp>
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/detail/type_name.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <iomanip>
#include <iosfwd>
#include <sstream>
#include <stdexcept>
#include <type_traits>
/**
\file boost/histogram/axis/ostream.hpp
Simple streaming operators for the builtin axis types.
The text representation is not guaranteed to be stable between versions of
Boost.Histogram. This header is only included by
[boost/histogram/ostream.hpp](histogram/reference.html#header.boost.histogram.ostream_hpp).
To use your own, include your own implementation instead of this header and do not
include
[boost/histogram/ostream.hpp](histogram/reference.html#header.boost.histogram.ostream_hpp).
*/
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace boost {
namespace histogram {
namespace detail {
inline const char* axis_suffix(const axis::transform::id&) { return ""; }
inline const char* axis_suffix(const axis::transform::log&) { return "_log"; }
inline const char* axis_suffix(const axis::transform::sqrt&) { return "_sqrt"; }
inline const char* axis_suffix(const axis::transform::pow&) { return "_pow"; }
template <class OStream, class T>
void stream_metadata(OStream& os, const T& t) {
detail::static_if<detail::is_streamable<T>>(
[&os](const auto& t) {
std::ostringstream oss;
oss << t;
if (!oss.str().empty()) { os << ", metadata=" << std::quoted(oss.str()); }
},
[&os](const auto&) {
os << ", metadata=" << boost::core::demangled_name(BOOST_CORE_TYPEID(T));
},
t);
auto ostream_any_impl(OStream& os, const T& t, priority<1>) -> decltype(os << t) {
return os << t;
}
template <class OStream, class T>
OStream& ostream_any_impl(OStream& os, const T&, priority<0>) {
return os << type_name<T>();
}
template <class OStream, class T>
OStream& ostream_any(OStream& os, const T& t) {
return ostream_any_impl(os, t, priority<1>{});
}
template <class OStream, class... Ts>
OStream& ostream_any_quoted(OStream& os, const std::basic_string<Ts...>& s) {
return os << std::quoted(s);
}
template <class OStream, class T>
OStream& ostream_any_quoted(OStream& os, const T& t) {
return ostream_any(os, t);
}
template <class... Ts, class T>
std::basic_ostream<Ts...>& ostream_metadata(std::basic_ostream<Ts...>& os, const T& t,
const char* prefix = ", ") {
std::streamsize count = 0;
{
auto g = make_count_guard(os, count);
ostream_any(os, t);
}
if (!count) return os;
os << prefix << "metadata=";
return ostream_any_quoted(os, t);
}
template <class OStream>
void stream_options(OStream& os, const unsigned bits) {
os << ", options=";
void ostream_options(OStream& os, const unsigned bits) {
bool first = true;
os << ", options=";
#define BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(x) \
if (bits & axis::option::x) { \
@@ -71,24 +104,6 @@ void stream_options(OStream& os, const unsigned bits) {
if (first) os << "none";
}
template <class OStream, class T>
void stream_transform(OStream&, const T&) {}
template <class OStream>
void stream_transform(OStream& os, const axis::transform::pow& t) {
os << ", power=" << t.power;
}
template <class OStream, class T>
void stream_value(OStream& os, const T& t) {
os << t;
}
template <class OStream, class... Ts>
void stream_value(OStream& os, const std::basic_string<Ts...>& t) {
os << std::quoted(t);
}
} // namespace detail
namespace axis {
@@ -104,40 +119,58 @@ std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const null_
template <class... Ts, class U>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const interval_view<U>& i) {
os << "[" << i.lower() << ", " << i.upper() << ")";
return os;
return os << "[" << i.lower() << ", " << i.upper() << ")";
}
template <class... Ts, class U>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const polymorphic_bin<U>& i) {
if (i.is_discrete())
os << static_cast<double>(i);
else
os << "[" << i.lower() << ", " << i.upper() << ")";
if (i.is_discrete()) return os << static_cast<double>(i);
return os << "[" << i.lower() << ", " << i.upper() << ")";
}
namespace transform {
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const id&) {
return os;
}
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const log&) {
return os << "transform::log{}";
}
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const sqrt&) {
return os << "transform::sqrt{}";
}
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const pow& p) {
return os << "transform::pow{" << p.power << "}";
}
} // namespace transform
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const regular<Us...>& a) {
os << "regular" << detail::axis_suffix(a.transform()) << "(" << a.size() << ", "
<< a.value(0) << ", " << a.value(a.size());
detail::stream_metadata(os, a.metadata());
detail::stream_options(os, a.options());
detail::stream_transform(os, a.transform());
os << ")";
return os;
os << "regular(";
const auto pos = os.tellp();
os << a.transform();
if (os.tellp() > pos) os << ", ";
os << a.size() << ", " << a.value(0) << ", " << a.value(a.size());
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const integer<Us...>& a) {
os << "integer(" << a.value(0) << ", " << a.value(a.size());
detail::stream_metadata(os, a.metadata());
detail::stream_options(os, a.options());
os << ")";
return os;
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class... Us>
@@ -145,10 +178,9 @@ std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const variable<Us...>& a) {
os << "variable(" << a.value(0);
for (index_type i = 1, n = a.size(); i <= n; ++i) { os << ", " << a.value(i); }
detail::stream_metadata(os, a.metadata());
detail::stream_options(os, a.options());
os << ")";
return os;
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class... Us>
@@ -156,31 +188,26 @@ std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const category<Us...>& a) {
os << "category(";
for (index_type i = 0, n = a.size(); i < n; ++i) {
detail::stream_value(os, a.value(i));
detail::ostream_any_quoted(os, a.value(i));
os << (i == (a.size() - 1) ? "" : ", ");
}
detail::stream_metadata(os, a.metadata());
detail::stream_options(os, a.options());
os << ")";
return os;
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class M>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const boolean<M>& a) {
os << "boolean(";
detail::ostream_metadata(os, a.metadata(), "");
return os << ")";
}
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const variant<Us...>& v) {
visit(
[&os](const auto& x) {
using A = detail::remove_cvref_t<decltype(x)>;
detail::static_if<detail::is_streamable<A>>(
[&os](const auto& x) { os << x; },
[](const auto&) {
BOOST_THROW_EXCEPTION(std::runtime_error(
detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(A)),
" is not streamable")));
},
x);
},
v);
visit([&os](const auto& x) { detail::ostream_any(os, x); }, v);
return os;
}

View File

@@ -7,30 +7,28 @@
#ifndef BOOST_HISTOGRAM_AXIS_POLYMORPHIC_BIN_HPP
#define BOOST_HISTOGRAM_AXIS_POLYMORPHIC_BIN_HPP
#include <boost/histogram/detail/meta.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace axis {
/**
Holds the bin data of a axis::variant.
Holds the bin data of an axis::variant.
The interface is a superset of the `value_bin_view` and `interval_bin_view`
classes. The methods value() and lower() return the same number. For a value bin,
upper() and lower() and center() return the same number, width() returns zero.
The interface is a superset of the axis::interval_view
class. In addition, the object is implicitly convertible to the value type,
returning the equivalent of a call to lower(). For discrete axes, lower() ==
upper(), and width() returns zero.
This is not a view like interval_bin_view or value_bin_view for two reasons.
This is not a view like axis::interval_view for two reasons.
- Sequential calls to lower() and upper() would have to each loop through
the variant types. This is likely to be slower than filling all the data in
one loop.
- polymorphic_bin may be created from a temporary instance of axis::variant,
like in the call histogram::axis(0). Storing a reference to the axis would
result in a dangling reference. Rather than specialing the code to handle
this, it seems easier to just use a value instead of a view here.
this, it seems easier to just use a value instead of a view.
*/
template <typename RealType>
template <class RealType>
class polymorphic_bin {
using value_type = RealType;
@@ -50,12 +48,12 @@ public:
/// Return width of bin.
value_type width() const noexcept { return upper() - lower(); }
template <typename BinType>
template <class BinType>
bool operator==(const BinType& rhs) const noexcept {
return equal_impl(detail::has_method_lower<BinType>(), rhs);
return equal_impl(rhs, 0);
}
template <typename BinType>
template <class BinType>
bool operator!=(const BinType& rhs) const noexcept {
return !operator==(rhs);
}
@@ -64,17 +62,17 @@ public:
bool is_discrete() const noexcept { return lower_or_value_ == upper_; }
private:
bool equal_impl(std::true_type, const polymorphic_bin& rhs) const noexcept {
bool equal_impl(const polymorphic_bin& rhs, int) const noexcept {
return lower_or_value_ == rhs.lower_or_value_ && upper_ == rhs.upper_;
}
template <typename BinType>
bool equal_impl(std::true_type, const BinType& rhs) const noexcept {
template <class BinType>
auto equal_impl(const BinType& rhs, decltype(rhs.lower(), 0)) const noexcept {
return lower() == rhs.lower() && upper() == rhs.upper();
}
template <typename BinType>
bool equal_impl(std::false_type, const BinType& rhs) const noexcept {
template <class BinType>
bool equal_impl(const BinType& rhs, float) const noexcept {
return is_discrete() && static_cast<value_type>(*this) == rhs;
}

View File

@@ -7,23 +7,60 @@
#ifndef BOOST_HISTOGRAM_AXIS_REGULAR_HPP
#define BOOST_HISTOGRAM_AXIS_REGULAR_HPP
#include <boost/assert.hpp>
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/interval_view.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/compressed_pair.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/histogram/detail/convert_integer.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
using get_scale_type_helper = typename T::value_type;
template <class T>
using get_scale_type = mp11::mp_eval_or<T, detail::get_scale_type_helper, T>;
struct one_unit {};
template <class T>
T operator*(T&& t, const one_unit&) {
return std::forward<T>(t);
}
template <class T>
T operator/(T&& t, const one_unit&) {
return std::forward<T>(t);
}
template <class T>
using get_unit_type_helper = typename T::unit_type;
template <class T>
using get_unit_type = mp11::mp_eval_or<one_unit, detail::get_unit_type_helper, T>;
template <class T, class R = get_scale_type<T>>
R get_scale(const T& t) {
return t / get_unit_type<T>();
}
} // namespace detail
namespace axis {
namespace transform {
@@ -31,46 +68,55 @@ namespace transform {
/// Identity transform for equidistant bins.
struct id {
/// Pass-through.
template <typename T>
template <class T>
static T forward(T&& x) noexcept {
return std::forward<T>(x);
}
/// Pass-through.
template <typename T>
template <class T>
static T inverse(T&& x) noexcept {
return std::forward<T>(x);
}
template <class Archive>
void serialize(Archive&, unsigned /* version */) {}
};
/// Log transform for equidistant bins in log-space.
struct log {
/// Returns log(x) of external value x.
template <typename T>
template <class T>
static T forward(T x) {
return std::log(x);
}
/// Returns exp(x) for internal value x.
template <typename T>
template <class T>
static T inverse(T x) {
return std::exp(x);
}
template <class Archive>
void serialize(Archive&, unsigned /* version */) {}
};
/// Sqrt transform for equidistant bins in sqrt-space.
struct sqrt {
/// Returns sqrt(x) of external value x.
template <typename T>
template <class T>
static T forward(T x) {
return std::sqrt(x);
}
/// Returns x^2 of internal value x.
template <typename T>
template <class T>
static T inverse(T x) {
return x * x;
}
template <class Archive>
void serialize(Archive&, unsigned /* version */) {}
};
/// Pow transform for equidistant bins in pow-space.
@@ -82,25 +128,30 @@ struct pow {
pow() = default;
/// Returns pow(x, power) of external value x.
template <typename T>
template <class T>
auto forward(T x) const {
return std::pow(x, power);
}
/// Returns pow(x, 1/power) of external value x.
template <typename T>
template <class T>
auto inverse(T x) const {
return std::pow(x, 1.0 / power);
}
bool operator==(const pow& o) const noexcept { return power == o.power; }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("power", power);
}
};
} // namespace transform
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
// Type envelope to mark value as step size
template <typename T>
template <class T>
struct step_type {
T value;
};
@@ -109,53 +160,79 @@ struct step_type {
/**
Helper function to mark argument as step size.
*/
template <typename T>
auto step(T&& t) {
return step_type<T&&>{std::forward<T>(t)};
template <class T>
step_type<T> step(T t) {
return step_type<T>{t};
}
/**
Axis for equidistant intervals on the real line.
/** Axis for equidistant intervals on the real line.
The most common binning strategy. Very fast. Binning is a O(1) operation.
If the axis has an overflow bin (the default), a value on the upper edge of the last
bin is put in the overflow bin. The axis range represents a semi-open interval.
If the overflow bin is deactivated, then a value on the upper edge of the last bin is
still counted towards the last bin. The axis range represents a closed interval.
The options `growth` and `circular` are mutually exclusive.
@tparam Value input value type, must be floating point.
@tparam Transform builtin or user-defined transform type.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option (all values allowed).
@tparam Options see boost::histogram::axis::option.
*/
template <class Value, class Transform, class MetaData, class Options>
class regular : public iterator_mixin<regular<Value, Transform, MetaData, Options>>,
protected detail::replace_default<Transform, transform::id> {
protected detail::replace_default<Transform, transform::id>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using transform_type = detail::replace_default<Transform, transform::id>;
using metadata_type = detail::replace_default<MetaData, std::string>;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type =
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
using unit_type = detail::get_unit_type<value_type>;
using internal_value_type = detail::get_scale_type<value_type>;
static_assert(std::is_floating_point<internal_value_type>::value,
"variable axis requires floating point type");
public:
constexpr regular() = default;
/** Construct n bins over real transformed range [start, stop).
*
* @param trans transform instance to use.
* @param n number of bins.
* @param start low edge of first bin.
* @param stop high edge of last bin.
* @param meta description of the axis (optional).
@param trans transform instance to use.
@param n number of bins.
@param start low edge of first bin.
@param stop high edge of last bin.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The constructor throws `std::invalid_argument` if n is zero, or if start and stop
produce an invalid range after transformation.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
regular(transform_type trans, unsigned n, value_type start, value_type stop,
metadata_type meta = {})
metadata_type meta = {}, options_type options = {})
: transform_type(std::move(trans))
, size_meta_(static_cast<index_type>(n), std::move(meta))
, metadata_base(std::move(meta))
, size_(static_cast<index_type>(n))
, min_(this->forward(detail::get_scale(start)))
, delta_(this->forward(detail::get_scale(stop)) - min_) {
if (size() == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
// static_asserts were moved here from class scope to satisfy deduction in gcc>=11
static_assert(std::is_nothrow_move_constructible<transform_type>::value,
"transform must be no-throw move constructible");
static_assert(std::is_nothrow_move_assignable<transform_type>::value,
"transform must be no-throw move assignable");
static_assert(std::is_floating_point<internal_value_type>::value,
"regular axis requires floating point type");
static_assert(!(options.test(option::circular) && options.test(option::growth)),
"circular and growth options are mutually exclusive");
if (size() <= 0) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
if (!std::isfinite(min_) || !std::isfinite(delta_))
BOOST_THROW_EXCEPTION(
std::invalid_argument("forward transform of start or stop invalid"));
@@ -164,58 +241,62 @@ public:
}
/** Construct n bins over real range [start, stop).
*
* @param n number of bins.
* @param start low edge of first bin.
* @param stop high edge of last bin.
* @param meta description of the axis (optional).
@param n number of bins.
@param start low edge of first bin.
@param stop high edge of last bin.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
*/
regular(unsigned n, value_type start, value_type stop, metadata_type meta = {})
: regular({}, n, start, stop, std::move(meta)) {}
explicit regular(unsigned n, value_type start, value_type stop, metadata_type meta = {},
options_type options = {})
: regular({}, n, start, stop, std::move(meta), options) {}
/** Construct bins with the given step size over real transformed range
* [start, stop).
*
* @param trans transform instance to use.
* @param step width of a single bin.
* @param start low edge of first bin.
* @param stop upper limit of high edge of last bin (see below).
* @param meta description of the axis (optional).
*
* The axis computes the number of bins as n = abs(stop - start) / step,
* rounded down. This means that stop is an upper limit to the actual value
* (start + n * step).
[start, stop).
@param trans transform instance to use.
@param step width of a single bin.
@param start low edge of first bin.
@param stop upper limit of high edge of last bin (see below).
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The axis computes the number of bins as n = abs(stop - start) / step,
rounded down. This means that stop is an upper limit to the actual value
(start + n * step).
*/
template <class T>
regular(transform_type trans, const step_type<T>& step, value_type start,
value_type stop, metadata_type meta = {})
explicit regular(transform_type trans, step_type<T> step, value_type start,
value_type stop, metadata_type meta = {}, options_type options = {})
: regular(trans, static_cast<index_type>(std::abs(stop - start) / step.value),
start,
start + static_cast<index_type>(std::abs(stop - start) / step.value) *
step.value,
std::move(meta)) {}
std::move(meta), options) {}
/** Construct bins with the given step size over real range [start, stop).
*
* @param step width of a single bin.
* @param start low edge of first bin.
* @param stop upper limit of high edge of last bin (see below).
* @param meta description of the axis (optional).
*
* The axis computes the number of bins as n = abs(stop - start) / step,
* rounded down. This means that stop is an upper limit to the actual value
* (start + n * step).
@param step width of a single bin.
@param start low edge of first bin.
@param stop upper limit of high edge of last bin (see below).
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The axis computes the number of bins as n = abs(stop - start) / step,
rounded down. This means that stop is an upper limit to the actual value
(start + n * step).
*/
template <class T>
regular(const step_type<T>& step, value_type start, value_type stop,
metadata_type meta = {})
: regular({}, step, start, stop, std::move(meta)) {}
explicit regular(step_type<T> step, value_type start, value_type stop,
metadata_type meta = {}, options_type options = {})
: regular({}, step, start, stop, std::move(meta), options) {}
/// Constructor used by algorithm::reduce to shrink and rebin (not for users).
regular(const regular& src, index_type begin, index_type end, unsigned merge)
: regular(src.transform(), (end - begin) / merge, src.value(begin), src.value(end),
src.metadata()) {
BOOST_ASSERT((end - begin) % merge == 0);
assert((end - begin) % merge == 0);
if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
}
@@ -239,29 +320,31 @@ public:
else
return -1;
}
// upper edge of last bin is inclusive if overflow bin is not present
if (!options_type::test(option::overflow) && z == 1) return size() - 1;
}
return size(); // also returned if x is NaN
}
/// Returns index and shift (if axis has grown) for the passed argument.
auto update(value_type x) noexcept {
BOOST_ASSERT(options_type::test(option::growth));
std::pair<index_type, index_type> update(value_type x) noexcept {
assert(options_type::test(option::growth));
const auto z = (this->forward(x / unit_type{}) - min_) / delta_;
if (z < 1) { // don't use i here!
if (z >= 0) {
const auto i = static_cast<axis::index_type>(z * size());
return std::make_pair(i, 0);
return {i, 0};
}
if (z != -std::numeric_limits<internal_value_type>::infinity()) {
const auto stop = min_ + delta_;
const auto i = static_cast<axis::index_type>(std::floor(z * size()));
min_ += i * (delta_ / size());
delta_ = stop - min_;
size_meta_.first() -= i;
return std::make_pair(0, -i);
size_ -= i;
return {0, -i};
}
// z is -infinity
return std::make_pair(-1, 0);
return {-1, 0};
}
// z either beyond range, infinite, or NaN
if (z < std::numeric_limits<internal_value_type>::infinity()) {
@@ -269,11 +352,11 @@ public:
const auto n = i - size() + 1;
delta_ /= size();
delta_ *= size() + n;
size_meta_.first() += n;
return std::make_pair(i, -n);
size_ += n;
return {i, -n};
}
// z either infinite or NaN
return std::make_pair(size(), 0);
return {size(), 0};
}
/// Return value for fractional index argument.
@@ -295,19 +378,16 @@ public:
}
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return size_meta_.first(); }
index_type size() const noexcept { return size_; }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
/// Returns reference to metadata.
metadata_type& metadata() noexcept { return size_meta_.second(); }
/// Returns reference to const metadata.
const metadata_type& metadata() const noexcept { return size_meta_.second(); }
template <class V, class T, class M, class O>
bool operator==(const regular<V, T, M, O>& o) const noexcept {
return detail::relaxed_equal(transform(), o.transform()) && size() == o.size() &&
detail::relaxed_equal(metadata(), o.metadata()) && min_ == o.min_ &&
delta_ == o.delta_;
return detail::relaxed_equal{}(transform(), o.transform()) && size() == o.size() &&
min_ == o.min_ && delta_ == o.delta_ &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class T, class M, class O>
bool operator!=(const regular<V, T, M, O>& o) const noexcept {
@@ -315,10 +395,16 @@ public:
}
template <class Archive>
void serialize(Archive&, unsigned);
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("transform", static_cast<transform_type&>(*this));
ar& make_nvp("size", size_);
ar& make_nvp("meta", this->metadata());
ar& make_nvp("min", min_);
ar& make_nvp("delta", delta_);
}
private:
detail::compressed_pair<index_type, metadata_type> size_meta_{0};
index_type size_{0};
internal_value_type min_{0}, delta_{1};
template <class V, class T, class M, class O>
@@ -328,29 +414,41 @@ private:
#if __cpp_deduction_guides >= 201606
template <class T>
regular(unsigned, T, T)->regular<detail::convert_integer<T, double>>;
template <class T>
regular(unsigned, T, T, const char*)->regular<detail::convert_integer<T, double>>;
regular(unsigned, T, T)
-> regular<detail::convert_integer<T, double>, transform::id, null_type>;
template <class T, class M>
regular(unsigned, T, T, M)->regular<detail::convert_integer<T, double>, transform::id, M>;
regular(unsigned, T, T, M) -> regular<detail::convert_integer<T, double>, transform::id,
detail::replace_cstring<std::decay_t<M>>>;
template <class Tr, class T>
regular(Tr, unsigned, T, T)->regular<detail::convert_integer<T, double>, Tr>;
template <class T, class M, unsigned B>
regular(unsigned, T, T, M, const option::bitset<B>&)
-> regular<detail::convert_integer<T, double>, transform::id,
detail::replace_cstring<std::decay_t<M>>, option::bitset<B>>;
template <class Tr, class T>
regular(Tr, unsigned, T, T, const char*)->regular<detail::convert_integer<T, double>, Tr>;
template <class Tr, class T, class = detail::requires_transform<Tr, T>>
regular(Tr, unsigned, T, T) -> regular<detail::convert_integer<T, double>, Tr, null_type>;
template <class Tr, class T, class M>
regular(Tr, unsigned, T, T, M)->regular<detail::convert_integer<T, double>, Tr, M>;
regular(Tr, unsigned, T, T, M) -> regular<detail::convert_integer<T, double>, Tr,
detail::replace_cstring<std::decay_t<M>>>;
template <class Tr, class T, class M, unsigned B>
regular(Tr, unsigned, T, T, M, const option::bitset<B>&)
-> regular<detail::convert_integer<T, double>, Tr,
detail::replace_cstring<std::decay_t<M>>, option::bitset<B>>;
#endif
/// Regular axis with circular option already set.
template <class Value = double, class MetaData = use_default, class Options = use_default>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using circular = regular<Value, transform::id, MetaData,
decltype(detail::replace_default<Options, option::overflow_t>{} |
option::circular)>;
#else
class circular;
#endif
} // namespace axis
} // namespace histogram

View File

@@ -7,66 +7,328 @@
#ifndef BOOST_HISTOGRAM_AXIS_TRAITS_HPP
#define BOOST_HISTOGRAM_AXIS_TRAITS_HPP
#include <boost/core/typeinfo.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/cat.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/histogram/detail/args_type.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/detail/try_cast.hpp>
#include <boost/histogram/detail/type_name.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <boost/variant2/variant.hpp>
#include <stdexcept>
#include <string>
#include <utility>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
using static_options_impl = axis::option::bitset<T::options()>;
template <class Axis>
struct value_type_deducer {
using type =
std::remove_cv_t<std::remove_reference_t<detail::arg_type<decltype(&Axis::index)>>>;
};
template <class FIntArg, class FDoubleArg, class T>
decltype(auto) value_method_switch(FIntArg&& iarg, FDoubleArg&& darg, const T& t) {
return static_if<has_method_value<T>>(
[](FIntArg&& iarg, FDoubleArg&& darg, const auto& t) {
using A = remove_cvref_t<decltype(t)>;
return static_if<std::is_same<arg_type<decltype(&A::value), 0>, int>>(
std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
},
[](FIntArg&&, FDoubleArg&&, const auto& t) -> double {
using A = remove_cvref_t<decltype(t)>;
BOOST_THROW_EXCEPTION(std::runtime_error(detail::cat(
boost::core::demangled_name(BOOST_CORE_TYPEID(A)), " has no value method")));
#ifndef _MSC_VER // msvc warns about unreachable return
return double{};
#endif
},
std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
template <class Axis>
auto traits_options(priority<2>) -> axis::option::bitset<Axis::options()>;
template <class Axis>
auto traits_options(priority<1>) -> decltype(&Axis::update, axis::option::growth_t{});
template <class Axis>
auto traits_options(priority<0>) -> axis::option::none_t;
template <class Axis>
auto traits_is_inclusive(priority<1>) -> std::integral_constant<bool, Axis::inclusive()>;
template <class Axis>
auto traits_is_inclusive(priority<0>)
-> decltype(traits_options<Axis>(priority<2>{})
.test(axis::option::underflow | axis::option::overflow));
template <class Axis>
auto traits_is_ordered(priority<1>) -> std::integral_constant<bool, Axis::ordered()>;
template <class Axis, class ValueType = typename value_type_deducer<Axis>::type>
auto traits_is_ordered(priority<0>) -> typename std::is_arithmetic<ValueType>::type;
template <class I, class D, class A,
class J = std::decay_t<arg_type<decltype(&A::value)>>>
decltype(auto) value_method_switch(I&& i, D&& d, const A& a, priority<1>) {
return static_if<std::is_same<J, axis::index_type>>(std::forward<I>(i),
std::forward<D>(d), a);
}
template <class R1, class R2, class FIntArg, class FDoubleArg, class T>
R2 value_method_switch_with_return_type(FIntArg&& iarg, FDoubleArg&& darg, const T& t) {
return static_if<has_method_value_with_convertible_return_type<T, R1>>(
[](FIntArg&& iarg, FDoubleArg&& darg, const auto& t) -> R2 {
using A = remove_cvref_t<decltype(t)>;
return static_if<std::is_same<arg_type<decltype(&A::value), 0>, int>>(
std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
},
[](FIntArg&&, FDoubleArg&&, const auto&) -> R2 {
BOOST_THROW_EXCEPTION(std::runtime_error(
detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(T)),
" has no value method or return type is not convertible to ",
boost::core::demangled_name(BOOST_CORE_TYPEID(R1)))));
#ifndef _MSC_VER // msvc warns about unreachable return
// conjure a value out of thin air to satisfy syntactic requirement
return *reinterpret_cast<R2*>(0);
#endif
},
std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
template <class I, class D, class A>
double value_method_switch(I&&, D&&, const A&, priority<0>) {
// comma trick to make all compilers happy; some would complain about
// unreachable code after the throw, others about a missing return
return BOOST_THROW_EXCEPTION(
std::runtime_error(type_name<A>() + " has no value method")),
double{};
}
struct variant_access {
template <class T, class Variant>
static auto get_if(Variant* v) noexcept {
using T0 = mp11::mp_first<std::decay_t<Variant>>;
return static_if<std::is_pointer<T0>>(
[](auto* vptr) {
using TP = mp11::mp_if<std::is_const<std::remove_pointer_t<T0>>, const T*, T*>;
auto ptp = variant2::get_if<TP>(vptr);
return ptp ? *ptp : nullptr;
},
[](auto* vptr) { return variant2::get_if<T>(vptr); }, &(v->impl));
}
template <class T0, class Visitor, class Variant>
static decltype(auto) visit_impl(mp11::mp_identity<T0>, Visitor&& vis, Variant&& v) {
return variant2::visit(std::forward<Visitor>(vis), v.impl);
}
template <class T0, class Visitor, class Variant>
static decltype(auto) visit_impl(mp11::mp_identity<T0*>, Visitor&& vis, Variant&& v) {
return variant2::visit(
[&vis](auto&& x) -> decltype(auto) { return std::forward<Visitor>(vis)(*x); },
v.impl);
}
template <class Visitor, class Variant>
static decltype(auto) visit(Visitor&& vis, Variant&& v) {
using T0 = mp11::mp_first<std::decay_t<Variant>>;
return visit_impl(mp11::mp_identity<T0>{}, std::forward<Visitor>(vis),
std::forward<Variant>(v));
}
};
template <class A>
decltype(auto) metadata_impl(A&& a, decltype(a.metadata(), 0)) {
return std::forward<A>(a).metadata();
}
template <class A>
axis::null_type& metadata_impl(A&&, float) {
static axis::null_type null_value;
return null_value;
}
} // namespace detail
namespace axis {
namespace traits {
/** Value type for axis type.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns the value type.
The value type is deduced from the argument of the `Axis::index` method. Const
references are decayed to the their value types, for example, the type deduced for
`Axis::index(const int&)` is `int`.
The deduction always succeeds if the axis type models the Axis concept correctly. Errors
come from violations of the concept, in particular, an index method that is templated or
overloaded is not allowed.
@tparam Axis axis type.
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using value_type = typename detail::value_type_deducer<Axis>::type;
#else
struct value_type;
#endif
/** Whether axis is continuous or discrete.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns a compile-time boolean.
If the boolean is true, the axis is continuous (covers a continuous range of values).
Otherwise it is discrete (covers discrete values).
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_continuous = typename std::is_floating_point<traits::value_type<Axis>>::type;
#else
struct is_continuous;
#endif
/** Meta-function to detect whether an axis is reducible.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and represents compile-time boolean which is true or false, depending on
whether the axis can be reduced with boost::histogram::algorithm::reduce().
An axis can be made reducible by adding a special constructor, see Axis concept for
details.
@tparam Axis axis type.
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_reducible = std::is_constructible<Axis, const Axis&, axis::index_type,
axis::index_type, unsigned>;
#else
struct is_reducible;
#endif
/** Get axis options for axis type.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns the boost::histogram::axis::option::bitset.
If Axis::options() is valid and constexpr, get_options is the corresponding
option type. Otherwise, it is boost::histogram::axis::option::growth_t, if the
axis has a method `update`, else boost::histogram::axis::option::none_t.
@tparam Axis axis type
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using get_options = decltype(detail::traits_options<Axis>(detail::priority<2>{}));
#else
struct get_options;
#endif
/** Meta-function to detect whether an axis is inclusive.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and represents compile-time boolean which is true or false, depending on
whether the axis is inclusive or not.
An inclusive axis has a bin for every possible input value. In other words, all
possible input values always end up in a valid cell and there is no need to keep track
of input tuples that need to be discarded. A histogram which consists entirely of
inclusive axes can be filled more efficiently, which can be a factor 2 faster.
An axis with underflow and overflow bins is always inclusive, but an axis may be
inclusive under other conditions. The meta-function checks for the method `constexpr
static bool inclusive()`, and uses the result. If this method is not present, it uses
get_options<Axis> and checks whether the underflow and overflow bits are present.
@tparam Axis axis type
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_inclusive = decltype(detail::traits_is_inclusive<Axis>(detail::priority<1>{}));
#else
struct is_inclusive;
#endif
/** Meta-function to detect whether an axis is ordered.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns a compile-time boolean. If the boolean is true, the axis is
ordered.
The meta-function checks for the method `constexpr static bool ordered()`, and uses the
result. If this method is not present, it returns true if the value type of the Axis is
arithmetic and false otherwise.
An ordered axis has a value type that is ordered, which means that indices i <
j < k implies either value(i) < value(j) < value(k) or value(i) > value(j) > value(k)
for all i,j,k. For example, the integer axis is ordered, but the category axis is not.
Axis which are not ordered must not have underflow bins, because they only have an
"other" category, which is identified with the overflow bin if it is available.
@tparam Axis axis type
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_ordered = decltype(detail::traits_is_ordered<Axis>(detail::priority<1>{}));
#else
struct is_ordered;
#endif
/** Returns axis options as unsigned integer.
See get_options for details.
@param axis any axis instance
*/
template <class Axis>
constexpr unsigned options(const Axis& axis) noexcept {
(void)axis;
return get_options<Axis>::value;
}
// specialization for variant
template <class... Ts>
unsigned options(const variant<Ts...>& axis) noexcept {
return axis.options();
}
/** Returns true if axis is inclusive or false.
See is_inclusive for details.
@param axis any axis instance
*/
template <class Axis>
constexpr bool inclusive(const Axis& axis) noexcept {
(void)axis;
return is_inclusive<Axis>::value;
}
// specialization for variant
template <class... Ts>
bool inclusive(const variant<Ts...>& axis) noexcept {
return axis.inclusive();
}
/** Returns true if axis is ordered or false.
See is_ordered for details.
@param axis any axis instance
*/
template <class Axis>
constexpr bool ordered(const Axis& axis) noexcept {
(void)axis;
return is_ordered<Axis>::value;
}
// specialization for variant
template <class... Ts>
bool ordered(const variant<Ts...>& axis) noexcept {
return axis.ordered();
}
/** Returns true if axis is continuous or false.
See is_continuous for details.
@param axis any axis instance
*/
template <class Axis>
constexpr bool continuous(const Axis& axis) noexcept {
(void)axis;
return is_continuous<Axis>::value;
}
// specialization for variant
template <class... Ts>
bool continuous(const variant<Ts...>& axis) noexcept {
return axis.continuous();
}
/** Returns axis size plus any extra bins for under- and overflow.
@param axis any axis instance
*/
template <class Axis>
index_type extent(const Axis& axis) noexcept {
const auto opt = options(axis);
return axis.size() + (opt & option::underflow ? 1 : 0) +
(opt & option::overflow ? 1 : 0);
}
/** Returns reference to metadata of an axis.
If the expression x.metadata() for an axis instance `x` (maybe const) is valid, return
@@ -77,57 +339,7 @@ namespace traits {
*/
template <class Axis>
decltype(auto) metadata(Axis&& axis) noexcept {
return detail::static_if<
detail::has_method_metadata<const detail::remove_cvref_t<Axis>>>(
[](auto&& a) -> decltype(auto) { return a.metadata(); },
[](auto &&) -> detail::copy_qualifiers<Axis, null_type> {
static null_type m;
return m;
},
std::forward<Axis>(axis));
}
/** Generates static axis option type for axis type.
WARNING: Doxygen does not render the synopsis correctly. This is a templated using
directive, which accepts axis type and returns boost::histogram::axis::option::bitset.
If Axis::options() is valid and constexpr, return the corresponding option type.
Otherwise, return boost::histogram::axis::option::growth_t, if the axis has a method
`update`, else return boost::histogram::axis::option::none_t.
@tparam Axis axis type
*/
template <class Axis>
using static_options = detail::mp_eval_or<
mp11::mp_if<detail::has_method_update<detail::remove_cvref_t<Axis>>, option::growth_t,
option::none_t>,
detail::static_options_impl, detail::remove_cvref_t<Axis>>;
/** Returns axis options as unsigned integer.
If axis.options() is valid, return the result. Otherwise, return
boost::histogram::axis::traits::static_options<decltype(axis)>::value.
@param axis any axis instance
*/
template <class Axis>
constexpr unsigned options(const Axis& axis) noexcept {
// cannot reuse static_options here, because this should also work for axis::variant
return detail::static_if<detail::has_method_options<Axis>>(
[](const auto& a) { return a.options(); },
[](const auto&) { return static_options<Axis>::value; }, axis);
}
/** Returns axis size plus any extra bins for under- and overflow.
@param axis any axis instance
*/
template <class Axis>
constexpr index_type extent(const Axis& axis) noexcept {
const auto opt = options(axis);
return axis.size() + (opt & option::underflow ? 1 : 0) +
(opt & option::overflow ? 1 : 0);
return detail::metadata_impl(std::forward<Axis>(axis), 0);
}
/** Returns axis value for index.
@@ -143,8 +355,8 @@ constexpr index_type extent(const Axis& axis) noexcept {
template <class Axis>
decltype(auto) value(const Axis& axis, real_index_type index) {
return detail::value_method_switch(
[index](const auto& a) { return a.value(static_cast<int>(index)); },
[index](const auto& a) { return a.value(index); }, axis);
[index](const auto& a) { return a.value(static_cast<index_type>(index)); },
[index](const auto& a) { return a.value(index); }, axis, detail::priority<1>{});
}
/** Returns axis value for index if it is convertible to target type or throws.
@@ -159,11 +371,8 @@ decltype(auto) value(const Axis& axis, real_index_type index) {
*/
template <class Result, class Axis>
Result value_as(const Axis& axis, real_index_type index) {
return detail::value_method_switch_with_return_type<Result, Result>(
[index](const auto& a) {
return static_cast<Result>(a.value(static_cast<int>(index)));
},
[index](const auto& a) { return static_cast<Result>(a.value(index)); }, axis);
return detail::try_cast<Result, std::runtime_error>(
axis::traits::value(axis, index)); // avoid conversion warning
}
/** Returns axis index for value.
@@ -174,38 +383,44 @@ Result value_as(const Axis& axis, real_index_type index) {
@param value argument to be passed to `index` method
*/
template <class Axis, class U>
auto index(const Axis& axis, const U& value) {
using V = detail::arg_type<decltype(&Axis::index)>;
return detail::static_if<std::is_convertible<U, V>>(
[&value](const auto& axis) {
using A = detail::remove_cvref_t<decltype(axis)>;
using V2 = detail::arg_type<decltype(&A::index)>;
return axis.index(static_cast<V2>(value));
},
[](const Axis&) -> index_type {
BOOST_THROW_EXCEPTION(std::invalid_argument(
detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(Axis)),
": cannot convert argument of type ",
boost::core::demangled_name(BOOST_CORE_TYPEID(U)), " to ",
boost::core::demangled_name(BOOST_CORE_TYPEID(V)))));
#ifndef _MSC_VER // msvc warns about unreachable return
return index_type{};
#endif
},
axis);
axis::index_type index(const Axis& axis, const U& value) noexcept(
std::is_convertible<U, value_type<Axis>>::value) {
return axis.index(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
}
/// @copydoc index(const Axis&, const U& value)
// specialization for variant
template <class... Ts, class U>
auto index(const variant<Ts...>& axis, const U& value) {
axis::index_type index(const variant<Ts...>& axis, const U& value) {
return axis.index(value);
}
/** Return axis rank (how many arguments it processes).
@param axis any axis instance
*/
// gcc workaround: must use unsigned int not unsigned as return type
template <class Axis>
constexpr unsigned int rank(const Axis& axis) {
(void)axis;
using T = value_type<Axis>;
// cannot use mp_eval_or since T could be a fixed-sized sequence
return mp11::mp_eval_if_not<detail::is_tuple<T>, mp11::mp_size_t<1>, mp11::mp_size,
T>::value;
}
// specialization for variant
// gcc workaround: must use unsigned int not unsigned as return type
template <class... Ts>
unsigned int rank(const axis::variant<Ts...>& axis) {
return detail::variant_access::visit(
[](const auto& a) { return axis::traits::rank(a); }, axis);
}
/** Returns pair of axis index and shift for the value argument.
Throws `std::invalid_argument` if the value argument is not implicitly convertible to
the argument expected by the `index` method. If the result of
boost::histogram::axis::traits::static_options<decltype(axis)> has the growth flag set,
boost::histogram::axis::traits::get_options<decltype(axis)> has the growth flag set,
call `update` method with the argument and return the result. Otherwise, call `index`
and return the pair of the result and a zero shift.
@@ -213,32 +428,22 @@ auto index(const variant<Ts...>& axis, const U& value) {
@param value argument to be passed to `update` or `index` method
*/
template <class Axis, class U>
std::pair<int, int> update(Axis& axis, const U& value) {
using V = detail::arg_type<decltype(&Axis::index)>;
return detail::static_if<std::is_convertible<U, V>>(
std::pair<index_type, index_type> update(Axis& axis, const U& value) noexcept(
std::is_convertible<U, value_type<Axis>>::value) {
return detail::static_if_c<get_options<Axis>::test(option::growth)>(
[&value](auto& a) {
using A = detail::remove_cvref_t<decltype(a)>;
return detail::static_if_c<static_options<A>::test(option::growth)>(
[&value](auto& a) { return a.update(value); },
[&value](auto& a) { return std::make_pair(a.index(value), 0); }, a);
return a.update(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
},
[](Axis&) -> std::pair<index_type, index_type> {
BOOST_THROW_EXCEPTION(std::invalid_argument(
detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(Axis)),
": cannot convert argument of type ",
boost::core::demangled_name(BOOST_CORE_TYPEID(U)), " to ",
boost::core::demangled_name(BOOST_CORE_TYPEID(V)))));
#ifndef _MSC_VER // msvc warns about unreachable return
return std::make_pair(index_type{}, index_type{});
#endif
[&value](auto& a) -> std::pair<index_type, index_type> {
return {axis::traits::index(a, value), 0};
},
axis);
}
/// @copydoc update(Axis& axis, const U& value)
// specialization for variant
template <class... Ts, class U>
auto update(variant<Ts...>& axis, const U& value) {
return axis.update(value);
std::pair<index_type, index_type> update(variant<Ts...>& axis, const U& value) {
return visit([&value](auto& a) { return a.update(value); }, axis);
}
/** Returns bin width at axis index.
@@ -254,7 +459,8 @@ template <class Axis>
decltype(auto) width(const Axis& axis, index_type index) {
return detail::value_method_switch(
[](const auto&) { return 0; },
[index](const auto& a) { return a.value(index + 1) - a.value(index); }, axis);
[index](const auto& a) { return a.value(index + 1) - a.value(index); }, axis,
detail::priority<1>{});
}
/** Returns bin width at axis index.
@@ -267,12 +473,13 @@ decltype(auto) width(const Axis& axis, index_type index) {
*/
template <class Result, class Axis>
Result width_as(const Axis& axis, index_type index) {
return detail::value_method_switch_with_return_type<Result, Result>(
return detail::value_method_switch(
[](const auto&) { return Result{}; },
[index](const auto& a) {
return static_cast<Result>(a.value(index + 1) - a.value(index));
return detail::try_cast<Result, std::runtime_error>(a.value(index + 1) -
a.value(index));
},
axis);
axis, detail::priority<1>{});
}
} // namespace traits

View File

@@ -8,18 +8,24 @@
#define BOOST_HISTOGRAM_AXIS_VARIABLE_HPP
#include <algorithm>
#include <boost/assert.hpp>
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/interval_view.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/compressed_pair.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/histogram/detail/convert_integer.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/limits.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <cmath>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
@@ -28,162 +34,212 @@ namespace boost {
namespace histogram {
namespace axis {
/**
Axis for non-equidistant bins on the real line.
/** Axis for non-equidistant bins on the real line.
Binning is a O(log(N)) operation. If speed matters and the problem domain
allows it, prefer a regular axis, possibly with a transform.
@tparam Value input value type, must be floating point.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option (all values allowed).
@tparam Allocator allocator to use for dynamic memory management.
*/
template <class Value, class MetaData, class Options, class Allocator>
class variable : public iterator_mixin<variable<Value, MetaData, Options, Allocator>> {
static_assert(std::is_floating_point<Value>::value,
"variable axis requires floating point type");
If the axis has an overflow bin (the default), a value on the upper edge of the last
bin is put in the overflow bin. The axis range represents a semi-open interval.
If the overflow bin is deactivated, then a value on the upper edge of the last bin is
still counted towards the last bin. The axis range represents a closed interval. This
is the desired behavior for random numbers drawn from a bounded interval, which is
usually closed.
@tparam Value input value type, must be floating point.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
@tparam Allocator allocator to use for dynamic memory management.
*/
template <class Value, class MetaData, class Options, class Allocator>
class variable : public iterator_mixin<variable<Value, MetaData, Options, Allocator>>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using metadata_type = detail::replace_default<MetaData, std::string>;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type =
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
using allocator_type = Allocator;
using vec_type = std::vector<Value, allocator_type>;
using vector_type = std::vector<Value, allocator_type>;
public:
explicit variable(allocator_type alloc = {}) : vec_meta_(std::move(alloc)) {}
constexpr variable() = default;
explicit variable(allocator_type alloc) : vec_(alloc) {}
/** Construct from iterator range of bin edges.
*
* \param begin begin of edge sequence.
* \param end end of edge sequence.
* \param meta description of the axis.
* \param alloc allocator instance to use.
/** Construct from forward iterator range of bin edges.
@param begin begin of edge sequence.
@param end end of edge sequence.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
The constructor throws `std::invalid_argument` if iterator range is invalid, if less
than two edges are provided or if bin edges are not in ascending order.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
template <class It, class = detail::requires_iterator<It>>
variable(It begin, It end, metadata_type meta = {}, allocator_type alloc = {})
: vec_meta_(vec_type(std::move(alloc)), std::move(meta)) {
if (std::distance(begin, end) <= 1)
BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
variable(It begin, It end, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: metadata_base(std::move(meta)), vec_(std::move(alloc)) {
// static_asserts were moved here from class scope to satisfy deduction in gcc>=11
static_assert(
std::is_floating_point<value_type>::value,
"current version of variable axis requires floating point type; "
"if you need a variable axis with an integral type, please submit an issue");
static_assert((!options.test(option::circular) && !options.test(option::growth)) ||
(options.test(option::circular) ^ options.test(option::growth)),
"circular and growth options are mutually exclusive");
auto& v = vec_meta_.first();
v.reserve(std::distance(begin, end));
v.emplace_back(*begin++);
while (begin != end) {
if (*begin <= v.back())
BOOST_THROW_EXCEPTION(
std::invalid_argument("input sequence must be strictly ascending"));
v.emplace_back(*begin++);
const auto n = std::distance(begin, end);
if (n < 0)
BOOST_THROW_EXCEPTION(
std::invalid_argument("end must be reachable by incrementing begin"));
if (n < 2) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 1 required"));
vec_.reserve(n);
vec_.emplace_back(*begin++);
bool strictly_ascending = true;
for (; begin != end; ++begin) {
strictly_ascending &= vec_.back() < *begin;
vec_.emplace_back(*begin);
}
if (!strictly_ascending)
BOOST_THROW_EXCEPTION(
std::invalid_argument("input sequence must be strictly ascending"));
}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class It, class A, class = detail::requires_iterator<It>,
class = detail::requires_allocator<A>>
variable(It begin, It end, metadata_type meta, A alloc)
: variable(begin, end, std::move(meta), {}, std::move(alloc)) {}
/** Construct variable axis from iterable range of bin edges.
*
* \param iterable iterable range of bin edges.
* \param meta description of the axis.
* \param alloc allocator instance to use.
@param iterable iterable range of bin edges.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
*/
template <class U, class = detail::requires_iterable<U>>
variable(const U& iterable, metadata_type meta = {}, allocator_type alloc = {})
: variable(std::begin(iterable), std::end(iterable), std::move(meta),
variable(const U& iterable, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: variable(std::begin(iterable), std::end(iterable), std::move(meta), options,
std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class U, class A, class = detail::requires_iterable<U>,
class = detail::requires_allocator<A>>
variable(const U& iterable, metadata_type meta, A alloc)
: variable(std::begin(iterable), std::end(iterable), std::move(meta), {},
std::move(alloc)) {}
/** Construct variable axis from initializer list of bin edges.
*
* @param list `std::initializer_list` of bin edges.
* @param meta description of the axis.
* @param alloc allocator instance to use.
@param list `std::initializer_list` of bin edges.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
*/
template <class U>
variable(std::initializer_list<U> list, metadata_type meta = {},
allocator_type alloc = {})
: variable(list.begin(), list.end(), std::move(meta), std::move(alloc)) {}
options_type options = {}, allocator_type alloc = {})
: variable(list.begin(), list.end(), std::move(meta), options, std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class U, class A, class = detail::requires_allocator<A>>
variable(std::initializer_list<U> list, metadata_type meta, A alloc)
: variable(list.begin(), list.end(), std::move(meta), {}, std::move(alloc)) {}
/// Constructor used by algorithm::reduce to shrink and rebin (not for users).
variable(const variable& src, index_type begin, index_type end, unsigned merge)
: vec_meta_(vec_type(src.get_allocator()), src.metadata()) {
BOOST_ASSERT((end - begin) % merge == 0);
: metadata_base(src), vec_(src.get_allocator()) {
assert((end - begin) % merge == 0);
if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
auto& vec = vec_meta_.first();
vec.reserve((end - begin) / merge);
const auto beg = src.vec_meta_.first().begin();
for (index_type i = begin; i <= end; i += merge) vec.emplace_back(*(beg + i));
vec_.reserve((end - begin) / merge);
const auto beg = src.vec_.begin();
for (index_type i = begin; i <= end; i += merge) vec_.emplace_back(*(beg + i));
}
/// Return index for value argument.
index_type index(value_type x) const noexcept {
const auto& v = vec_meta_.first();
if (options_type::test(option::circular)) {
const auto a = v[0];
const auto b = v[size()];
const auto a = vec_[0];
const auto b = vec_[size()];
x -= std::floor((x - a) / (b - a)) * (b - a);
}
return static_cast<index_type>(std::upper_bound(v.begin(), v.end(), x) - v.begin() -
1);
// upper edge of last bin is inclusive if overflow bin is not present
if (!options_type::test(option::overflow) && x == vec_.back()) return size() - 1;
return static_cast<index_type>(std::upper_bound(vec_.begin(), vec_.end(), x) -
vec_.begin() - 1);
}
auto update(value_type x) noexcept {
std::pair<index_type, index_type> update(value_type x) noexcept {
const auto i = index(x);
if (std::isfinite(x)) {
auto& vec = vec_meta_.first();
if (0 <= i) {
if (i < size()) return std::make_pair(i, 0);
const auto d = value(size()) - value(size() - 0.5);
x = std::nextafter(x, std::numeric_limits<value_type>::max());
x = std::max(x, vec.back() + d);
vec.push_back(x);
return std::make_pair(i, -1);
x = std::nextafter(x, (std::numeric_limits<value_type>::max)());
x = (std::max)(x, vec_.back() + d);
vec_.push_back(x);
return {i, -1};
}
const auto d = value(0.5) - value(0);
x = std::min(x, value(0) - d);
vec.insert(vec.begin(), x);
return std::make_pair(0, -i);
x = (std::min)(x, value(0) - d);
vec_.insert(vec_.begin(), x);
return {0, -i};
}
return std::make_pair(x < 0 ? -1 : size(), 0);
return {x < 0 ? -1 : size(), 0};
}
/// Return value for fractional index argument.
value_type value(real_index_type i) const noexcept {
const auto& v = vec_meta_.first();
if (options_type::test(option::circular)) {
auto shift = std::floor(i / size());
i -= shift * size();
double z;
const auto k = static_cast<index_type>(std::modf(i, &z));
const auto a = v[0];
const auto b = v[size()];
return (1.0 - z) * v[k] + z * v[k + 1] + shift * (b - a);
const auto a = vec_[0];
const auto b = vec_[size()];
return (1.0 - z) * vec_[k] + z * vec_[k + 1] + shift * (b - a);
}
if (i < 0) return detail::lowest<value_type>();
if (i == size()) return v.back();
if (i == size()) return vec_.back();
if (i > size()) return detail::highest<value_type>();
const auto k = static_cast<index_type>(i); // precond: i >= 0
const real_index_type z = i - k;
return (1.0 - z) * v[k] + z * v[k + 1];
// check z == 0 needed to avoid returning nan when vec_[k + 1] is infinity
return (1.0 - z) * vec_[k] + (z == 0 ? 0 : z * vec_[k + 1]);
}
/// Return bin for index argument.
auto bin(index_type idx) const noexcept { return interval_view<variable>(*this, idx); }
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept {
return static_cast<index_type>(vec_meta_.first().size()) - 1;
}
index_type size() const noexcept { return static_cast<index_type>(vec_.size()) - 1; }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
/// Returns reference to metadata.
metadata_type& metadata() noexcept { return vec_meta_.second(); }
/// Returns reference to const metadata.
const metadata_type& metadata() const noexcept { return vec_meta_.second(); }
template <class V, class M, class O, class A>
bool operator==(const variable<V, M, O, A>& o) const noexcept {
const auto& a = vec_meta_.first();
const auto& b = o.vec_meta_.first();
const auto& a = vec_;
const auto& b = o.vec_;
return std::equal(a.begin(), a.end(), b.begin(), b.end()) &&
detail::relaxed_equal(metadata(), o.metadata());
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class M, class O, class A>
@@ -192,13 +248,16 @@ public:
}
/// Return allocator instance.
auto get_allocator() const { return vec_meta_.first().get_allocator(); }
auto get_allocator() const { return vec_.get_allocator(); }
template <class Archive>
void serialize(Archive&, unsigned);
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("seq", vec_);
ar& make_nvp("meta", this->metadata());
}
private:
detail::compressed_pair<vec_type, metadata_type> vec_meta_;
vector_type vec_;
template <class V, class M, class O, class A>
friend class variable;
@@ -206,32 +265,38 @@ private:
#if __cpp_deduction_guides >= 201606
template <class U, class T = detail::convert_integer<U, double>>
variable(std::initializer_list<U>)->variable<T>;
template <class T>
variable(std::initializer_list<T>)
-> variable<detail::convert_integer<T, double>, null_type>;
template <class U, class T = detail::convert_integer<U, double>>
variable(std::initializer_list<U>, const char*)->variable<T>;
template <class T, class M>
variable(std::initializer_list<T>, M)
-> variable<detail::convert_integer<T, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
template <class U, class M, class T = detail::convert_integer<U, double>>
variable(std::initializer_list<U>, M)->variable<T, M>;
template <class T, class M, unsigned B>
variable(std::initializer_list<T>, M, const option::bitset<B>&)
-> variable<detail::convert_integer<T, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>,
option::bitset<B>>;
template <
class Iterable,
class T = detail::convert_integer<
detail::remove_cvref_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>>
variable(Iterable)->variable<T>;
template <class Iterable, class = detail::requires_iterable<Iterable>>
variable(Iterable) -> variable<
detail::convert_integer<
std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
null_type>;
template <
class Iterable,
class T = detail::convert_integer<
detail::remove_cvref_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>>
variable(Iterable, const char*)->variable<T>;
template <class Iterable, class M>
variable(Iterable, M) -> variable<
detail::convert_integer<
std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
template <
class Iterable, class M,
class T = detail::convert_integer<
detail::remove_cvref_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>>
variable(Iterable, M)->variable<T, M>;
template <class Iterable, class M, unsigned B>
variable(Iterable, M, const option::bitset<B>&) -> variable<
detail::convert_integer<
std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>, option::bitset<B>>;
#endif

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Hans Dembinski
// Copyright 2015-2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
@@ -7,24 +7,19 @@
#ifndef BOOST_HISTOGRAM_AXIS_VARIANT_HPP
#define BOOST_HISTOGRAM_AXIS_VARIANT_HPP
#include <boost/core/typeinfo.hpp>
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/polymorphic_bin.hpp>
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/cat.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/bind.hpp>
#include <boost/mp11/function.hpp>
#include <boost/mp11/list.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/detail/type_name.hpp>
#include <boost/histogram/detail/variant_proxy.hpp>
#include <boost/mp11/algorithm.hpp> // mp_contains
#include <boost/mp11/list.hpp> // mp_first
#include <boost/throw_exception.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/get.hpp>
#include <boost/variant/static_visitor.hpp>
#include <boost/variant/variant.hpp>
#include <ostream>
#include <boost/variant2/variant.hpp>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
@@ -32,27 +27,20 @@ namespace boost {
namespace histogram {
namespace axis {
template <class T, class... Us>
T* get_if(variant<Us...>* v);
template <class T, class... Us>
const T* get_if(const variant<Us...>* v);
/// Polymorphic axis type
template <class... Ts>
class variant : public iterator_mixin<variant<Ts...>> {
using impl_type = boost::variant<Ts...>;
using raw_types = mp11::mp_transform<detail::remove_cvref_t, impl_type>;
using impl_type = boost::variant2::variant<Ts...>;
template <class T>
using is_bounded_type = mp11::mp_contains<raw_types, detail::remove_cvref_t<T>>;
using is_bounded_type = mp11::mp_contains<variant, std::decay_t<T>>;
template <typename T>
template <class T>
using requires_bounded_type = std::enable_if_t<is_bounded_type<T>::value>;
// maybe metadata_type or const metadata_type, if bounded type is const
using metadata_type = std::remove_reference_t<decltype(
traits::metadata(std::declval<mp11::mp_first<impl_type>>()))>;
using metadata_type =
std::remove_const_t<std::remove_reference_t<decltype(traits::metadata(
std::declval<std::remove_pointer_t<mp11::mp_first<variant>>>()))>>;
public:
// cannot import ctors with using directive, it breaks gcc and msvc
@@ -62,10 +50,10 @@ public:
variant(variant&&) = default;
variant& operator=(variant&&) = default;
template <typename T, typename = requires_bounded_type<T>>
template <class T, class = requires_bounded_type<T>>
variant(T&& t) : impl(std::forward<T>(t)) {}
template <typename T, typename = requires_bounded_type<T>>
template <class T, class = requires_bounded_type<T>>
variant& operator=(T&& t) {
impl = std::forward<T>(t);
return *this;
@@ -80,14 +68,13 @@ public:
variant& operator=(const variant<Us...>& u) {
visit(
[this](const auto& u) {
using U = detail::remove_cvref_t<decltype(u)>;
using U = std::decay_t<decltype(u)>;
detail::static_if<is_bounded_type<U>>(
[this](const auto& u) { this->operator=(u); },
[](const auto&) {
BOOST_THROW_EXCEPTION(std::runtime_error(detail::cat(
boost::core::demangled_name(BOOST_CORE_TYPEID(U)),
" is not convertible to a bounded type of ",
boost::core::demangled_name(BOOST_CORE_TYPEID(variant)))));
BOOST_THROW_EXCEPTION(std::runtime_error(
detail::type_name<U>() + " is not convertible to a bounded type of " +
detail::type_name<variant>()));
},
u);
},
@@ -97,30 +84,44 @@ public:
/// Return size of axis.
index_type size() const {
return visit([](const auto& x) { return x.size(); }, *this);
return visit([](const auto& a) -> index_type { return a.size(); }, *this);
}
/// Return options of axis or option::none_t if axis has no options.
unsigned options() const {
return visit([](const auto& x) { return axis::traits::options(x); }, *this);
return visit([](const auto& a) { return traits::options(a); }, *this);
}
/// Returns true if the axis is inclusive or false.
bool inclusive() const {
return visit([](const auto& a) { return traits::inclusive(a); }, *this);
}
/// Returns true if the axis is ordered or false.
bool ordered() const {
return visit([](const auto& a) { return traits::ordered(a); }, *this);
}
/// Returns true if the axis is continuous or false.
bool continuous() const {
return visit([](const auto& a) { return traits::continuous(a); }, *this);
}
/// Return reference to const metadata or instance of null_type if axis has no
/// metadata.
const metadata_type& metadata() const {
metadata_type& metadata() const {
return visit(
[](const auto& a) -> const metadata_type& {
[](const auto& a) -> metadata_type& {
using M = decltype(traits::metadata(a));
return detail::static_if<std::is_same<M, const metadata_type&>>(
[](const auto& a) -> const metadata_type& { return traits::metadata(a); },
[](const auto&) -> const metadata_type& {
BOOST_THROW_EXCEPTION(std::runtime_error(detail::cat(
"cannot return metadata of type ",
boost::core::demangled_name(BOOST_CORE_TYPEID(M)),
" through axis::variant interface which uses type ",
boost::core::demangled_name(BOOST_CORE_TYPEID(metadata_type)),
return detail::static_if<std::is_same<M, metadata_type&>>(
[](const auto& a) -> metadata_type& { return traits::metadata(a); },
[](const auto&) -> metadata_type& {
BOOST_THROW_EXCEPTION(std::runtime_error(
"cannot return metadata of type " + detail::type_name<M>() +
" through axis::variant interface which uses type " +
detail::type_name<metadata_type>() +
"; use boost::histogram::axis::get to obtain a reference "
"of this axis type")));
"of this axis type"));
},
a);
},
@@ -131,166 +132,214 @@ public:
/// metadata.
metadata_type& metadata() {
return visit(
[](auto&& a) -> metadata_type& {
[](auto& a) -> metadata_type& {
using M = decltype(traits::metadata(a));
return detail::static_if<std::is_same<M, metadata_type&>>(
[](auto& a) -> metadata_type& { return traits::metadata(a); },
[](auto&) -> metadata_type& {
BOOST_THROW_EXCEPTION(std::runtime_error(detail::cat(
"cannot return metadata of type ",
boost::core::demangled_name(BOOST_CORE_TYPEID(M)),
" through axis::variant interface which uses type ",
boost::core::demangled_name(BOOST_CORE_TYPEID(metadata_type)),
BOOST_THROW_EXCEPTION(std::runtime_error(
"cannot return metadata of type " + detail::type_name<M>() +
" through axis::variant interface which uses type " +
detail::type_name<metadata_type>() +
"; use boost::histogram::axis::get to obtain a reference "
"of this axis type")));
"of this axis type"));
},
a);
},
*this);
}
/// Return index for value argument.
/// Throws std::invalid_argument if axis has incompatible call signature.
/** Return index for value argument.
Throws std::invalid_argument if axis has incompatible call signature.
*/
template <class U>
index_type index(const U& u) const {
return visit([&u](const auto& a) { return traits::index(a, u); }, *this);
}
/// Return value for index argument.
/// Only works for axes with value method that returns something convertible
/// to double and will throw a runtime_error otherwise, see
/// axis::traits::value().
/** Return value for index argument.
Only works for axes with value method that returns something convertible
to double and will throw a runtime_error otherwise, see
axis::traits::value().
*/
double value(real_index_type idx) const {
return visit([idx](const auto& a) { return traits::value_as<double>(a, idx); },
*this);
}
/// Return bin for index argument.
/// Only works for axes with value method that returns something convertible
/// to double and will throw a runtime_error otherwise, see
/// axis::traits::value().
/** Return bin for index argument.
Only works for axes with value method that returns something convertible
to double and will throw a runtime_error otherwise, see
axis::traits::value().
*/
auto bin(index_type idx) const {
return visit(
[idx](const auto& a) {
return detail::value_method_switch_with_return_type<double,
polymorphic_bin<double>>(
return detail::value_method_switch(
[idx](const auto& a) { // axis is discrete
const auto x = a.value(idx);
const double x = traits::value_as<double>(a, idx);
return polymorphic_bin<double>(x, x);
},
[idx](const auto& a) { // axis is continuous
return polymorphic_bin<double>(a.value(idx), a.value(idx + 1));
const double x1 = traits::value_as<double>(a, idx);
const double x2 = traits::value_as<double>(a, idx + 1);
return polymorphic_bin<double>(x1, x2);
},
a);
a, detail::priority<1>{});
},
*this);
}
template <class... Us>
bool operator==(const variant<Us...>& u) const {
return visit([&u](const auto& x) { return u == x; }, *this);
}
template <class T>
bool operator==(const T& t) const {
// boost::variant::operator==(T) implemented only to fail, cannot use it
auto tp = get_if<T>(this);
return tp && detail::relaxed_equal(*tp, t);
}
template <class T>
bool operator!=(const T& t) const {
return !operator==(t);
}
template <class Archive>
void serialize(Archive& ar, unsigned);
template <class Visitor, class Variant>
friend auto visit(Visitor&&, Variant &&)
-> detail::visitor_return_type<Visitor, Variant>;
template <class T, class... Us>
friend T& get(variant<Us...>& v);
template <class T, class... Us>
friend const T& get(const variant<Us...>& v);
template <class T, class... Us>
friend T&& get(variant<Us...>&& v);
template <class T, class... Us>
friend T* get_if(variant<Us...>* v);
template <class T, class... Us>
friend const T* get_if(const variant<Us...>* v);
void serialize(Archive& ar, unsigned /* version */) {
detail::variant_proxy<variant> p{*this};
ar& make_nvp("variant", p);
}
private:
boost::variant<Ts...> impl;
impl_type impl;
friend struct detail::variant_access;
friend struct boost::histogram::unsafe_access;
};
/// Apply visitor to variant.
template <class Visitor, class Variant>
auto visit(Visitor&& vis, Variant&& var)
-> detail::visitor_return_type<Visitor, Variant> {
return boost::apply_visitor(std::forward<Visitor>(vis), var.impl);
// specialization for empty argument list, useful for meta-programming
template <>
class variant<> {};
/// Apply visitor to variant (reference).
template <class Visitor, class... Us>
decltype(auto) visit(Visitor&& vis, variant<Us...>& var) {
return detail::variant_access::visit(vis, var);
}
/// Return lvalue reference to T, throws unspecified exception if type does not
/// match.
template <class T, class... Us>
T& get(variant<Us...>& v) {
return boost::get<T>(v.impl);
/// Apply visitor to variant (movable reference).
template <class Visitor, class... Us>
decltype(auto) visit(Visitor&& vis, variant<Us...>&& var) {
return detail::variant_access::visit(vis, std::move(var));
}
/// Return rvalue reference to T, throws unspecified exception if type does not
/// match.
template <class T, class... Us>
T&& get(variant<Us...>&& v) {
return boost::get<T>(std::move(v.impl));
}
/// Return const reference to T, throws unspecified exception if type does not
/// match.
template <class T, class... Us>
const T& get(const variant<Us...>& v) {
return boost::get<T>(v.impl);
/// Apply visitor to variant (const reference).
template <class Visitor, class... Us>
decltype(auto) visit(Visitor&& vis, const variant<Us...>& var) {
return detail::variant_access::visit(vis, var);
}
/// Returns pointer to T in variant or null pointer if type does not match.
template <class T, class... Us>
T* get_if(variant<Us...>* v) {
return boost::relaxed_get<T>(&(v->impl));
auto get_if(variant<Us...>* v) {
return detail::variant_access::template get_if<T>(v);
}
/// Returns pointer to const T in variant or null pointer if type does not
/// match.
/// Returns pointer to const T in variant or null pointer if type does not match.
template <class T, class... Us>
const T* get_if(const variant<Us...>* v) {
return boost::relaxed_get<T>(&(v->impl));
auto get_if(const variant<Us...>* v) {
return detail::variant_access::template get_if<T>(v);
}
/// Return reference to T, throws std::runtime_error if type does not match.
template <class T, class... Us>
decltype(auto) get(variant<Us...>& v) {
auto tp = get_if<T>(&v);
if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
return *tp;
}
/// Return movable reference to T, throws unspecified exception if type does not match.
template <class T, class... Us>
decltype(auto) get(variant<Us...>&& v) {
auto tp = get_if<T>(&v);
if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
return std::move(*tp);
}
/// Return const reference to T, throws unspecified exception if type does not match.
template <class T, class... Us>
decltype(auto) get(const variant<Us...>& v) {
auto tp = get_if<T>(&v);
if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
return *tp;
}
// pass-through version of visit for generic programming
template <class Visitor, class T>
decltype(auto) visit(Visitor&& vis, T&& var) {
return std::forward<Visitor>(vis)(std::forward<T>(var));
}
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
// pass-through version of get for generic programming
template <class T, class U>
decltype(auto) get(U&& u) {
return static_cast<detail::copy_qualifiers<U, T>>(u);
return std::forward<U>(u);
}
// pass-through version of get_if for generic programming
template <class T, class U>
T* get_if(U* u) {
return std::is_same<T, detail::remove_cvref_t<U>>::value ? reinterpret_cast<T*>(u)
: nullptr;
auto get_if(U* u) {
return reinterpret_cast<T*>(std::is_same<T, std::decay_t<U>>::value ? u : nullptr);
}
// pass-through version of get_if for generic programming
template <class T, class U>
const T* get_if(const U* u) {
return std::is_same<T, detail::remove_cvref_t<U>>::value ? reinterpret_cast<const T*>(u)
: nullptr;
auto get_if(const U* u) {
return reinterpret_cast<const T*>(std::is_same<T, std::decay_t<U>>::value ? u
: nullptr);
}
/** Compare two variants.
Return true if the variants point to the same concrete axis type and the types compare
equal. Otherwise return false.
*/
template <class... Us, class... Vs>
bool operator==(const variant<Us...>& u, const variant<Vs...>& v) noexcept {
return visit([&](const auto& vi) { return u == vi; }, v);
}
/** Compare variant with a concrete axis type.
Return true if the variant point to the same concrete axis type and the types compare
equal. Otherwise return false.
*/
template <class... Us, class T>
bool operator==(const variant<Us...>& u, const T& t) noexcept {
using V = variant<Us...>;
return detail::static_if_c<(mp11::mp_contains<V, T>::value ||
mp11::mp_contains<V, T*>::value ||
mp11::mp_contains<V, const T*>::value)>(
[&](const auto& t) {
using U = std::decay_t<decltype(t)>;
const U* tp = detail::variant_access::template get_if<U>(&u);
return tp && detail::relaxed_equal{}(*tp, t);
},
[&](const auto&) { return false; }, t);
}
template <class T, class... Us>
bool operator==(const T& t, const variant<Us...>& u) noexcept {
return u == t;
}
/// The negation of operator==.
template <class... Us, class... Ts>
bool operator!=(const variant<Us...>& u, const variant<Ts...>& t) noexcept {
return !(u == t);
}
/// The negation of operator==.
template <class... Us, class T>
bool operator!=(const variant<Us...>& u, const T& t) noexcept {
return !(u == t);
}
/// The negation of operator==.
template <class T, class... Us>
bool operator!=(const T& t, const variant<Us...>& u) noexcept {
return u != t;
}
#endif
} // namespace axis
} // namespace histogram