Skip to content

Commit

Permalink
improve docstrings for axes, explain move issue (#382)
Browse files Browse the repository at this point in the history
  • Loading branch information
HDembinski authored Dec 26, 2022
1 parent 652c7f8 commit 0494608
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 81 deletions.
2 changes: 1 addition & 1 deletion doc/concepts/Axis.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ An [*Axis] maps input values to indices. It holds state specific to that axis, l
* `a` and `b` are values of type `A`
* `i` and `j` are indices of type [headerref boost/histogram/fwd.hpp `boost::histogram::axis::index_type`]
* `n` is a value of type `unsigned`
* `M` is a metadata type that is [@https://en.cppreference.com/w/cpp/named_req/CopyConstructible CopyConstructible] and [@https://en.cppreference.com/w/cpp/named_req/CopyAssignable CopyAssignable] and *nothrow* [@https://en.cppreference.com/w/cpp/named_req/MoveAssignable MoveAssignable].
* `M` is a metadata type that is [@https://en.cppreference.com/w/cpp/named_req/DefaultConstructible DefaultConstructible], [@https://en.cppreference.com/w/cpp/named_req/CopyConstructible CopyConstructible] and [@https://en.cppreference.com/w/cpp/named_req/CopyAssignable CopyAssignable]. It it supports moves, it must be *nothrow* [@https://en.cppreference.com/w/cpp/named_req/MoveAssignable MoveAssignable].
* `ar` is a value of an archive with Boost.Serialization semantics

[table Valid expressions
Expand Down
10 changes: 7 additions & 3 deletions include/boost/histogram/axis/boolean.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,14 @@ class boolean : public iterator_mixin<boolean<MetaData>>,

public:
/** Construct a boolean axis.
*
* @param meta description of the axis.
@param meta description of the axis.
The constructor is nothrow if meta is nothrow move constructible.
*/
explicit boolean(metadata_type meta = {}) : metadata_base(std::move(meta)) {}
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); }
Expand Down
43 changes: 25 additions & 18 deletions include/boost/histogram/axis/category.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ namespace axis {

/** 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
are not part of the set. Binning has O(N) complexity, but with a very small
factor. For small N (the typical use case) it beats other kinds of lookup.
@tparam Value input value type, must be equal-comparable.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
@tparam Allocator allocator to use for dynamic memory management.
The options `underflow` and `circular` are not allowed. The options `growth`
and `overflow` are mutually exclusive.
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
are not part of the set. Binning has O(N) complexity, but with a very small
factor. For small N (the typical use case) it beats other kinds of lookup.
@tparam Value input value type, must be equal-comparable.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
@tparam Allocator allocator to use for dynamic memory management.
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>>,
Expand All @@ -56,13 +56,20 @@ class category : public iterator_mixin<category<Value, MetaData, Options, Alloca
constexpr category() = default;
explicit category(allocator_type alloc) : vec_(alloc) {}

/** Construct from iterator range of unique values.
/** 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).
@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 = {}, options_type options = {},
Expand Down
58 changes: 31 additions & 27 deletions include/boost/histogram/axis/integer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ namespace axis {

/** 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.
The options `growth` and `circular` are mutually exclusive.
@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>>,
Expand All @@ -47,23 +49,6 @@ class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
using options_type =
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;

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_type::test(option::circular | option::growth) ||
(options_type::test(option::circular) ^
options_type::test(option::growth)),
"circular and growth options are mutually exclusive");

static_assert(std::is_floating_point<value_type>::value ||
(!options_type::test(option::circular) &&
!options_type::test(option::growth)) ||
(!options_type::test(option::overflow) &&
!options_type::test(option::underflow)),
"circular or growing integer axis with integral type "
"cannot have entries in underflow or overflow bins");

using local_index_type = std::conditional_t<std::is_integral<value_type>::value,
index_type, real_index_type>;

Expand All @@ -72,18 +57,37 @@ class integer : public iterator_mixin<integer<Value, MetaData, Options>>,

/** 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 (optional).
@param options see boost::histogram::axis::option (optional).
@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 = {},
options_type options = {})
: metadata_base(std::move(meta))
, size_(static_cast<index_type>(stop - start))
, min_(start) {
(void)options;
if (!(stop >= 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"));
}

Expand Down
15 changes: 12 additions & 3 deletions include/boost/histogram/axis/metadata_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ 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,
Expand All @@ -45,15 +54,15 @@ class metadata_base {
return *this;
}

private:
mutable metadata_type data_;

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
Expand Down
46 changes: 26 additions & 20 deletions include/boost/histogram/axis/regular.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,20 +167,20 @@ step_type<T> step(T t) {

/** Axis for equidistant intervals on the real line.
The most common binning strategy. Very fast. Binning is a O(1) operation.
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 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.
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.
@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.
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.
*/
template <class Value, class Transform, class MetaData, class Options>
class regular : public iterator_mixin<regular<Value, Transform, MetaData, Options>>,
Expand All @@ -202,12 +202,19 @@ class regular : public iterator_mixin<regular<Value, Transform, MetaData, Option

/** 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 options see boost::histogram::axis::option (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 = {}, options_type options = {})
Expand All @@ -223,10 +230,9 @@ class regular : public iterator_mixin<regular<Value, Transform, MetaData, Option
"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)) ||
(options.test(option::circular) ^ options.test(option::growth)),
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 (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"));
Expand Down
30 changes: 21 additions & 9 deletions include/boost/histogram/axis/variable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,20 @@ class variable : public iterator_mixin<variable<Value, MetaData, Options, Alloca
constexpr variable() = default;
explicit variable(allocator_type alloc) : vec_(alloc) {}

/** Construct from iterator range of bin edges.
/** 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).
@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 = {}, options_type options = {},
Expand All @@ -89,16 +96,21 @@ class variable : public iterator_mixin<variable<Value, MetaData, Options, Alloca
(options.test(option::circular) ^ options.test(option::growth)),
"circular and growth options are mutually exclusive");

if (std::distance(begin, end) < 2)
BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
const auto n = std::distance(begin, end);
if (n < 0)
BOOST_THROW_EXCEPTION(
std::invalid_argument("end must be reachable by incrementing begin"));

vec_.reserve(std::distance(begin, end));
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"));
Expand Down

0 comments on commit 0494608

Please sign in to comment.