From 5dd50985aea1774f4e5cbe38c7bd26c88860cbc1 Mon Sep 17 00:00:00 2001 From: Hans Dembinski Date: Tue, 5 Mar 2019 01:52:01 +0100 Subject: [PATCH] Concepts (#166) * concept docs rewritten, adapting to tabular style as in Beast and cppreference.com * update the readme with correct links and text * removed necessity to add `storage_tag` member type to storage classes --- README.md | 102 ++++++------ doc/Jamfile | 5 - doc/concepts.qbk | 69 +------- doc/concepts/Accumulator.qbk | 80 +++++++++ doc/concepts/Axis.qbk | 114 +++++++++++++ doc/concepts/DiscreteAxis.qbk | 73 +++++++++ doc/concepts/IntervalAxis.qbk | 76 +++++++++ doc/concepts/Storage.qbk | 154 ++++++++++++++++++ doc/concepts/Transform.qbk | 63 +++++++ doc/guide.qbk | 8 +- doc/histogram.qbk | 2 +- doc/rationale.qbk | 4 +- .../boost/histogram/accumulators/ostream.hpp | 4 + include/boost/histogram/axis/category.hpp | 2 +- include/boost/histogram/axis/ostream.hpp | 4 + include/boost/histogram/axis/variable.hpp | 2 +- include/boost/histogram/detail/meta.hpp | 40 ++--- include/boost/histogram/fwd.hpp | 21 ++- include/boost/histogram/make_profile.hpp | 9 +- include/boost/histogram/ostream.hpp | 4 + include/boost/histogram/unlimited_storage.hpp | 1 - test/meta_test.cpp | 19 +-- test/utility_meta.hpp | 11 +- 23 files changed, 690 insertions(+), 177 deletions(-) create mode 100644 doc/concepts/Accumulator.qbk create mode 100644 doc/concepts/Axis.qbk create mode 100644 doc/concepts/DiscreteAxis.qbk create mode 100644 doc/concepts/IntervalAxis.qbk create mode 100644 doc/concepts/Storage.qbk create mode 100644 doc/concepts/Transform.qbk diff --git a/README.md b/README.md index 0989c128..188c0df6 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,53 @@ master | [![Build Status Travis](https://travis-ci.com/HDembinski/histogram.svg This **header-only** open-source library provides an easy-to-use state-of-the-art multi-dimensional [histogram](https://en.wikipedia.org/wiki/Histogram) class for the professional statistician and everyone who needs to counts things. The histogram is easy to use for the casual user and yet very customizable. It does not restrict the power-user. The library offers a unique safety guarantee: cell counts *cannot overflow* or *be capped* in the standard configuration. And it can do more than counting. The histogram can be equipped with arbitrary accumulators to compute means, medians, and whatever you fancy in each cell. The library is very fast single-threaded (see benchmarks) and several parallelization options are provided for multi-threaded programming. -This project was developed for inclusion in [Boost](http://www.boost.org) and passed Boost review in September 2018. The plan is to have a first official Boost-release in April 2019 with the upcoming version 1.70. Of course, you can use it already now (but pick the latest release!). The source code is licensed under the [Boost Software License](http://www.boost.org/LICENSE_1_0.txt). +The project has passed Boost review in September 2018 and is going to be first released with [Boost-1.70](http://www.boost.org) in April. The source code is licensed under the [Boost Software License](http://www.boost.org/LICENSE_1_0.txt). -Check out the [full documentation](http://hdembinski.github.io/histogram/doc/html/index.html). [Python bindings](https://github.com/hdembinski/histogram-python) to this library are available elsewhere. +Check out the [full documentation](https://www.boost.org/doc/libs/develop/libs/histogram/doc/html/index.html). [Python bindings](https://github.com/hdembinski/histogram-python) to this library are available elsewhere. + +## Code example + +The following stripped-down example was taken from the [Getting started](https://www.boost.org/doc/libs/develop/libs/histogram/doc/html/histogram/getting_started.html) section in the documentation. Have a look into the docs to see the full version with comments and more examples. + +Example: Fill a 1d-histogram + +```cpp +#include +#include // used here for printing +#include // for std::ref + +int main() { + using namespace boost::histogram; + + auto h = make_histogram( + axis::regular<>(6, -1.0, 2.0, "x") + ); + + // fill histogram + auto data = { -0.4, 1.1, 0.3, 1.7 }; + std::for_each(data.begin(), data.end(), std::ref(h)); + + // iterate over bins + for (auto x : indexed(h)) { + std::cout << boost::format("bin %2i [%4.1f, %4.1f): %i\n") + % x.index() % x.bin().lower() % x.bin().upper() % *x; + } + + std::cout << std::flush; + + /* program output: + + bin -1 [-inf, -1.0): 1 + bin 0 [-1.0, -0.5): 1 + bin 1 [-0.5, -0.0): 1 + bin 2 [-0.0, 0.5): 2 + bin 3 [ 0.5, 1.0): 0 + bin 4 [ 1.0, 1.5): 1 + bin 5 [ 1.5, 2.0): 1 + bin 6 [ 2.0, inf): 2 + */ +} +``` ## Features @@ -42,66 +86,16 @@ Check out the [full documentation](http://hdembinski.github.io/histogram/doc/htm 3. The histogram can be configured to hold an arbitrary accumulator in each cell instead of a simple counter. Extra values can be passed to the histogram, for example, to compute the mean and variance of values which fall into the same cell. This can be used to compute variance estimates for each cell. These are useful when histograms are to be compared quantitatively and if a statistical model is fitted to the cell values. 4. Builtin axis types can configured to only accept dimensional quantities, like those from [Boost.Units](https://www.boost.org/doc/libs/release/libs/units/). This means you get an error if you try to fill a length when the histogram axis expects a time, for example. -## Code example - -The following stripped-down example was taken from the [Getting started](http://hdembinski.github.io/histogram/doc/html/histogram/getting_started.html) section in the documentation. Have a look into the docs to see the full version with comments and more examples. - -Example: Fill a 1d-histogram - -```cpp -#include -#include // used here for printing -#include // for std::ref - -int main() { - namespace bh = boost::histogram; - - auto h = bh::make_histogram( - bh::axis::regular<>(6, -1.0, 2.0, "x") - ); - - // fill histogram - auto data = { -0.4, 1.1, 0.3, 1.7 }; - std::for_each(data.begin(), data.end(), std::ref(h)); - - // iterate over bins - for (auto x : bh::indexed(h)) { - std::cout << boost::format("bin %2i [%4.1f, %4.1f): %i\n") - % x.index() % x.bin().lower() % x.bin().upper() % *x; - } - - std::cout << std::flush; - - /* program output: - - bin -1 [-inf, -1.0): 1 - bin 0 [-1.0, -0.5): 1 - bin 1 [-0.5, -0.0): 1 - bin 2 [-0.0, 0.5): 2 - bin 3 [ 0.5, 1.0): 0 - bin 4 [ 1.0, 1.5): 1 - bin 5 [ 1.5, 2.0): 1 - bin 6 [ 2.0, inf): 2 - */ -} -``` - ## Benchmarks -Boost.Histogram is more flexible and faster than other C/C++ libraries: +Boost.Histogram is more flexible and faster than other C/C++ libraries. It was compared to: - [GNU Scientific Library](https://www.gnu.org/software/gsl) - [ROOT framework from CERN](https://root.cern.ch) -Details on the benchmark are given in the [documentation](http://hdembinski.github.io/histogram/doc/html/histogram/benchmarks.html). +Details on the benchmark are given in the [documentation](https://www.boost.org/doc/libs/develop/libs/histogram/doc/html/histogram/benchmarks.html). ## What users say **John Buonagurio** | Manager at [**Exponent®**](www.exponent.com) *"I just wanted to say 'thanks' for your awesome Histogram library. I'm working on a software package for processing meteorology data and I'm using it to generate wind roses with the help of Qt and QwtPolar. Looks like you thought of just about everything here – the circular axis type was practically designed for this application, everything 'just worked'."* - -## State of project - -Development is currently targets the 4.0 release, the first one to appear officially in Boost (**Boost-1.70** is planned for April 2019). More than 500 individual tests make sure that the implementation works as expected. Full documentation is available. User feedback is appreciated! - -The library was reviewed in September 2018 by the Boost Community under review manager Mateusz Loskot. It was **conditionally accepted** with requests to improve aspects of the interface and documentation. Current development is focusing on implementing these requests. Code-breaking changes of the interface are currently happening on the develop branch. If you want to use the library in production code, please use the [latest release](https://github.com/boostorg/histogram/releases). After the library is released as part of Boost, the interface will be kept stable. diff --git a/doc/Jamfile b/doc/Jamfile index 4a130b01..d8cd6d9f 100644 --- a/doc/Jamfile +++ b/doc/Jamfile @@ -27,11 +27,6 @@ doxygen reference JAVADOC_AUTOBRIEF=YES USE_MATHJAX=NO SEARCH_INCLUDES=YES - "EXCLUDE=\"../../../boost/histogram/fwd.hpp\" \\ - \"../../../boost/histogram/ostream.hpp\" \\ - \"../../../boost/histogram/axis/ostream.hpp\" \\ - \"../../../boost/histogram/accumulators/ostream.hpp\" \\ - " EXCLUDE_SYMBOLS=detail WARNINGS=YES WARN_IF_DOC_ERROR=YES diff --git a/doc/concepts.qbk b/doc/concepts.qbk index 469b6146..a9f6edd5 100644 --- a/doc/concepts.qbk +++ b/doc/concepts.qbk @@ -2,68 +2,11 @@ Users can extend the library with various new types whose concepts are defined here. -[section:axis AxisType] - -An `AxisType` converts input values into bin indices. - -An `AxisType` is required to: - -* be default/copy/move constructable -* be copy/move assignable -* be equal comparable -* have a nested type `value_type` reflecting the type of the input values (may be a const reference if the input value is expensive to copy) -* have a nested type `bin_type`, which is a type that represents the bin, typically a semi-open interval (may be a const reference if the bin type is expensive to copy) -* have the following methods: - * `int index(value_type x) const`: takes an input value and returns the bin index - * `bin_type operator[](int index) const`: takes an index and returns the corresponding bin instance -* optionally, be streamable by implementation a free function - * `std::ostream operator<<(std::ostream&, const axis_type&)` -* optionally, be serializable, by implementing a member function template - * `template void serialize(Archive& ar, unsigned /* version */)` - -The latter two are not needed, if the histogram that uses the custom axis type is never serialized or streamed. - -It is recommended to take a look at the existing axis types, like [classref boost::histogram::axis::integer], which serve as templates to create new ones. - -[endsect] - -[section:transform TransformType] - -To do, explain the Transform concept. - -[endsect] - -[section:storage StorageType] - -A `StorageType` handles memory for the bin counters and provides a uniform interface for incrementing bin counters and reading their values. - -A `StorageType` is required to: - -* be default/copy/move constructable -* be copy/move assignable -* be equal comparable -* have nested types - * `allocator_type` - * `element_type`, which represent the bin count - * `const_reference`, const reference of bin count - * `scale_type`, type to scale the storage counters -* have the following methods and operators: - * `void reset(std::size_t n)` which prepares n counters initialized to zero - * `std::size_t size() const` get number of counters - * `void increase(std::size_t index)` increment bin counter - * `template void add(std::size_t index, const T& x)` add value to bin counter - * `const_reference operator[](std::size_t index) const` read bin counter - * `storage_type& operator+=(const storage_type& other)` add another storage - * `storage_type& operator*=(const scale_type& x)` multiply by scale type -* optionally, it can have the following method to support weighted increments: - * `template void add(std::size_t index, const boost::histogram::detail::weight_type& w)` - -[endsect] - -[section:accumulator AccumulatorType] - -TODO - -[endsect] +[include concepts/Axis.qbk] +[include concepts/DiscreteAxis.qbk] +[include concepts/IntervalAxis.qbk] +[include concepts/Transform.qbk] +[include concepts/Storage.qbk] +[include concepts/Accumulator.qbk] [endsect] diff --git a/doc/concepts/Accumulator.qbk b/doc/concepts/Accumulator.qbk new file mode 100644 index 00000000..ee41a956 --- /dev/null +++ b/doc/concepts/Accumulator.qbk @@ -0,0 +1,80 @@ +[section:Accumulator Accumulator] + +An [*Accumulator] is a functor which consumes the argument to update some internal state. The state can be read with member functions or free functions. Must be [@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]. + +[heading Required features] + +* `A` is a type meeting the requirements of [*Accumulator] +* `a` and `b` are values of type `A` +* `ts...` is a pack of values + +[table Valid expressions +[[Expression] [Return type] [Semantics, Pre/Post-conditions]] +[ + [`a(ts...)` or `++a`] + [] + [ + Either a call operator accepting a fixed number of arguments must be implemented, or the pre-increment operator. + ] +] +[ + [`a == b`] + [`bool`] + [ + Returns `true` if all state variables compare equal. Otherwise returns `false`. + ] +] +[ + [`a != b`] + [`bool`] + [ + Must be implemented if `a == b` is implemented and must be equal to `!(a == b)`. + ] +] +] + +[heading Optional features] + +* `A` is a type meeting the requirements of [*Accumulator] +* `a` and `b` are values of type `A` + +[table Valid expressions +[[Expression] [Return type] [Semantics, Pre/Post-conditions]] +[ + [`a += b`] + [`A&`] + [ + Adds a second accumulator `b` of type `A`. The result must be the same as if `a` had been filled with all arguments of `b`. + ] +] +[ + [`a *= x`] + [`A&`] + [ + Scales the accumulator state by the real value `x`. The result must be the same as if `a` had been filled with all arguments scaled by `x`. + ] +] +[ + [`os << a`] + [`std::basic_ostream&`] + [ + `os` is a value of type `std::basic_ostream`. Streams a text representation of the axis. May not mutate `a`. + ] +] +[ + [`ar & a`] + [] + [ + `ar` is a value of an archive with Boost.Serialization semantics. Serializes `a` to the archive or loads serialized state from the archive. + ] +] +] + +[heading Models] + +* [classref boost::histogram::accumulators::sum] +* [classref boost::histogram::accumulators::weighted_sum] +* [classref boost::histogram::accumulators::mean] +* [classref boost::histogram::accumulators::weighted_mean] + +[endsect] diff --git a/doc/concepts/Axis.qbk b/doc/concepts/Axis.qbk new file mode 100644 index 00000000..2435b0da --- /dev/null +++ b/doc/concepts/Axis.qbk @@ -0,0 +1,114 @@ +[section:Axis Axis] + +An [*Axis] maps input values to indices. It holds state specific to that axis, like the number of bins and any metadata. Must be [@https://en.cppreference.com/w/cpp/named_req/CopyConstructible CopyConstructible] and [@https://en.cppreference.com/w/cpp/named_req/CopyAssignable CopyAssignable]. + +[heading Associated Types] + +* [link histogram.concepts.DiscreteAxis [*DiscreteAxis]] +* [link histogram.concepts.IntervalAxis [*IntervalAxis]] + +[heading Required features] + +* `A` is a type meeting the requirements of [*Axis] +* `a` is a value of type `A` +* `I` is an alias for [headerref boost/histogram/fwd.hpp `boost::histogram::index_type`] + +[table Valid expressions +[[Expression] [Return type] [Semantics, Pre/Post-conditions]] +[ + [`a.size()`] + [`I`] + [ + Const member function which returns the number of bins of the axis. All indices from `0` to `a.size() - 1` must be valid and address a bin of the axis. + ] +] +[ + [`a.index(v)`] + [`I`] + [ + Const member function which maps a value `v` to an index. The mapping must be injective: each value must be uniquely mapped to one index. If the value is not covered by the axis, return either `-1` or `a.size()`. The value `-1` indicates that the value is lower than the lowest value covered by the axis. The value `a.size()` indicates that the value is above the uppermost value covered by the axis. By convention, /NaN/-values are mapped to `a.size()`. + ] +] +[ + [`a.get_allocator()`] + [`Alloc`] + [ + Const member function which returns the allocator `Alloc` used by this axis. May be omitted if `A` does not use allocators. If this member function exists, also a special constructor must exists so that `A(a.get_allocator())` is a valid expression. + ] +] +] + +[heading Optional features] + +* `A` is a type meeting the requirements of [*Axis] +* `a` and `b` are values of type `A` +* `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] + +[table Valid expressions +[[Expression] [Return type] [Semantics, Pre/Post-conditions]] +[ + [`a.update(v)`] + [`std::pair`] + [ + Non-const member function which maps a value to an index (first argument of the returned pair) and offset (second argument of the returned pair). If the value is not covered by the axis, this method may grow the current axis size (`old_size`) by the number of bins needed to contain the value or more (`new_size`). If the value is below the lowest value covered by the axis, return index `0` and offset `new_size - old_size`. If the value is above the uppermost value covered by the axis, return index `new_size - 1` and a negative offset `old_size - new_size`. If the value is outside, but the axis is not enlarged, then return an index equivalent to `a.index(v)` and offset `0`. + ] +] +[ + [`a.options()`] + [`unsigned`] + [ + Static constexpr member function which returns the [headerref boost/histogram/axis/option.hpp axis options] for this axis. + ] +] +[ + [`a.metadata()`] + [`M` or `const M&`] + [ + Const member function which returns a copy of or a const reference to the metadata associated with the axis, usually a label string. + ] +] +[ + [`a.metadata()`] + [`M&`] + [ + Likewise, but non-const member function which returns a reference to the metadata. If this member function exists, also the previous one must exist. + ] +] +[ + [`a == b`] + [`bool`] + [ + Returns `true` if all state variables compare equal, including any metadata. Otherwise returns `false`. If `a == b` is implemented, also `a != b` must be implemented. If this binary operator is not implemented, the library considers the arguments equal, if and only if their types are the same. + ] +] +[ + [`a != b`] + [`bool`] + [ + Must be implemented if `a == b` is implemented and must be equal to `!(a == b)`. + ] +] +[ + [`os << a`] + [`std::basic_ostream&`] + [ + `os` is a value of type `std::basic_ostream`. Streams a text representation of the axis. May not mutate `a`. + ] +] +[ + [`ar & a`] + [] + [ + `ar` is a value of an archive with Boost.Serialization semantics. Serializes `a` to the archive or loads serialized state from the archive. + ] +] +] + +[heading Models] + +* [classref boost::histogram::axis::category] +* [classref boost::histogram::axis::integer] +* [classref boost::histogram::axis::regular] +* [classref boost::histogram::axis::variable] + +[endsect] diff --git a/doc/concepts/DiscreteAxis.qbk b/doc/concepts/DiscreteAxis.qbk new file mode 100644 index 00000000..6076bdd3 --- /dev/null +++ b/doc/concepts/DiscreteAxis.qbk @@ -0,0 +1,73 @@ +[section:DiscreteAxis DiscreteAxis] + +A [*DiscreteAxis] is one of two optional refinements of the [link histogram.concepts.Axis [*Axis]] concept, the other one is the [link histogram.concepts.IntervalAxis IntervalAxis]. This concept is for values that do not form intervals, and for axes with intervals that contain exactly one value. + +[heading Associated Types] + +* [link histogram.concepts.Axis [*Axis]] +* [link histogram.concepts.IntervalAxis [*IntervalAxis]] + +[heading Requirements] + +* `A` is a type meeting the requirements of [*DiscreteAxis] +* `a` is a value of type `A` +* `V` is the type accepted for conversion into an index +* `v` is a value of type `V` +* `i` is a value of type [headerref boost/histogram/fwd.hpp `boost::histogram::index_type`] +* `AxisIter` is an /RandomAccessIterator/ over the bins of `A` +* `ReAxisIter` is a reverse /RandomAccessIterator/ over the bins of `A` + +[table Valid expressions +[[Expression] [Return type] [Semantics, Pre/Post-conditions]] +[ + [`a.value(i)`] + [`V`] + [ + Const member function which maps an index to a value. The mapping must be injective: each index must be uniquely mapped to one value. The effect must be exactly the inverse of `a.index(v)`. The return value may be a const reference, if the lifetime of the referred object is equal to the lifetime of the axis. + ] +] +[ + [`a.bin(i)`] + [`V`] + [ + Must have the same effect as `a.value(i)`. + ] +] +[ + [`s.begin()`] + [`AxisIter`] + [ + Const member function which return an iterator to the bin with index `0`. + ] +] +[ + [`s.end()`] + [`AxisIter`] + [ + Const member function which returns an iterator to the bin with index `s.size()`. + ] +] +[ + [`s.rbegin()`] + [`ReAxisIter`] + [ + Const member function which return a reverse iterator to the bin with index `s.size()-1`. + ] +] +[ + [`s.rend()`] + [`ReAxisIter`] + [ + Const member function which returns an iterator to the bin with index `-1`. + ] +] +] + +[tip The complete iterator interface can be added to a user-defined axis which implements `a.bin(i)` by inheriting from the [classref boost::histogram::axis::iterator_mixin iterator_mixin].] + +[heading Models] + +* [classref boost::histogram::axis::category] +* [classref boost::histogram::axis::integer], if first template parameter is [headerref boost/histogram/fwd.hpp `boost::histogram::index_type`] + +[endsect] diff --git a/doc/concepts/IntervalAxis.qbk b/doc/concepts/IntervalAxis.qbk new file mode 100644 index 00000000..25d31833 --- /dev/null +++ b/doc/concepts/IntervalAxis.qbk @@ -0,0 +1,76 @@ +[section:IntervalAxis IntervalAxis] + +A [*IntervalAxis] is one of two optional refinements of the [link histogram.concepts.Axis [*Axis]] concept, the other one is the [link histogram.concepts.DiscreteAxis DiscreteAxis]. It is for ordered values that form intervals with a well-defined lower and upper edge, and a center. Each bin represents an interval of values. + +[heading Associated Types] + +* [link histogram.concepts.Axis [*Axis]] +* [link histogram.concepts.DiscreteAxis [*DiscreteAxis]] + +[heading Requirements] + +* `A` is a type meeting the requirements of [*IntervalAxis] +* `a` is a value of type `A` +* `V` is the type accepted for conversion into an index +* `B` is the type that represents the bin interval +* `v` is a value of type `V` +* `i` is a value of type [headerref boost/histogram/fwd.hpp `boost::histogram::index_type`] +* `j` is a value of type [headerref boost/histogram/fwd.hpp `boost::histogram::real_index_type`] +* `AxisIter` is an /RandomAccessIterator/ over the bins of `A` +* `ReAxisIter` is a reverse /RandomAccessIterator/ over the bins of `A` + +[table Valid expressions +[[Expression] [Return type] [Semantics, Pre/Post-conditions]] +[ + [`a.value(i)`] + [`V`] + [ + Const member function which maps an index to a value. The mapping must be injective: each index must be uniquely mapped to one value. The result of `a.value(a.index(v))` should agree to very high precision with `v` (the mapping may not be exact due to the finite precision of floating point computations). The return value may be a const reference, if the lifetime of the referred object is equal to the lifetime of the axis. `a.value(j)` is expected to return the lower edge of the bin, `a.value(j+1)` the upper edge, and `a.value(j + 0.5)` the center. + ] +] +[ + [`a.bin(i)`] + [`B`] + [ + Const member function which returns an instance that represents the current bin. Nothing about the type is required, but it is recommended that the type has the methods `B::lower()`, `B::upper()`, and `B::center()` similar to the types used by the builtin axis models. The return value may be a const reference, if the lifetime of the referred object is equal to the lifetime of the axis. + ] +] +[ + [`s.begin()`] + [`AxisIter`] + [ + Const member function which return an iterator to the bin with index `0`. + ] +] +[ + [`s.end()`] + [`AxisIter`] + [ + Const member function which returns an iterator to the bin with index `s.size()`. + ] +] +[ + [`s.rbegin()`] + [`ReAxisIter`] + [ + Const member function which return a reverse iterator to the bin with index `s.size()-1`. + ] +] +[ + [`s.rend()`] + [`ReAxisIter`] + [ + Const member function which returns an iterator to the bin with index `-1`. + ] +] +] + +[tip The complete iterator interface can be added to a user-defined axis which implements `a.bin(i)` by inheriting from the [classref boost::histogram::axis::iterator_mixin iterator_mixin].] + +[heading Models] + +* [classref boost::histogram::axis::regular] +* [classref boost::histogram::axis::variable] +* [classref boost::histogram::axis::integer], if first template parameter is a floating point type + +[endsect] diff --git a/doc/concepts/Storage.qbk b/doc/concepts/Storage.qbk new file mode 100644 index 00000000..83b06e65 --- /dev/null +++ b/doc/concepts/Storage.qbk @@ -0,0 +1,154 @@ +[section:Storage Storage] + +A [*Storage] handles memory for the bin counters and provides a uniform vector-like interface for accessing cell values for reading and writing. Must be [@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]. + +[heading Required features] + +* `S` is a type meeting the requirements of [*Storage] +* `s` is a value of types `S` +* `i` and `n` are values of type `std::size_t` + +[table Valid expressions +[[Expression] [Return type] [Semantics, Pre/Post-conditions]] +[ + [`S::value_type`] + [] + [ + Cell element type, may be either an integral type, floating-point type, or a type meeting the requirements of [link histogram.concepts.Accumulator [*Accumulator]]. + ] +] +[ + [`S::reference`] + [] + [ + `S::value_type&` or a proxy class which acts like a reference. + ] +] +[ + [`S::const_reference`] + [] + [ + `const S::value_type&` or a proxy class which acts like a const reference. Implicitly convertible to `S::value_type`. + ] +] +[ + [`S::iterator`] + [] + [ + Returns an STL-compliant iterator type which dereferences to `S::reference`. + ] +] +[ + [`S::const_iterator`] + [] + [ + Returns an STL-compliant iterator type which dereferences to `S::const_reference`. + ] +] +[ + [`s.size()`] + [`std::size_t`] + [ + Const member function which returns the current number of cells in the storage. + ] +] +[ + [`s.reset(n)`] + [] + [ + Non-const member function which discards current cell values, changes storage size to `n` and initializes all cells to the default-constructed state. + ] +] +[ + [`s.begin()`] + [`S::iterator`] + [ + Non-const member function which returns the iterator to the first storage cell. + ] +] +[ + [`s.begin()`] + [`S::const_iterator`] + [ + Likewise, but a const member function which returns the const_iterator. + ] +] +[ + [`s.end()`] + [`S::iterator`] + [ + Member function which returns the iterator to the cell after the last valid storage cell. + ] +] +[ + [`s.end()`] + [`S::const_iterator`] + [ + Likewise, but a const member function which returns the const_iterator. + ] +] +[ + [`s[i]`] + [`S::reference`] + [ + Member function which returns a reference to the cell which is addressed by `i`. The index `i` must be valid: `i < s.size()`. + ] +] +[ + [`s[i]`] + [`S::const_reference`] + [ + Likewise, but a const member function which returns a const reference. + ] +] +[ + [`s == t`] + [`bool`] + [ + `t` is another value of a type which meets the requirements of [*Storage]. Returns `true` if arguments have the same number of cells and all cells compare equal. Otherwise returns `false`. + ] +] +[ + [`s.get_allocator()`] + [`Alloc`] + [ + Const member function which returns the allocator `Alloc` used by `S`. May be omitted if `S` does not use allocators. If this member function exists, also a special constructor must exists so that `S(s.get_allocator())` is a valid expression. + ] +] +] + +[heading Optional features] + +* `S` is a type meeting the requirements of [*Storage] +* `s` is a value of types `S` +* `x` is convertible to `double` +* `ar` is a value of an archive with Boost.Serialization semantics + +[table Valid expressions +[[Expression] [Return type] [Semantics, Pre/Post-conditions]] +[ + [`s *= x`] + [`S&`] + [ + Scales all cell values by the factor `x` and returns a reference to self. + ] +] +[ + [`ar & s`] + [] + [ + Serializes `s` to the archive or loads serialized state from the archive. + ] +] +] + +[heading Models] + +* [classref boost::histogram::unlimited_storage] +* [classref boost::histogram::storage_adaptor] +* [classref boost::histogram::dense_storage] +* [classref boost::histogram::weight_storage] +* [classref boost::histogram::profile_storage] +* [classref boost::histogram::weighted_profile_storage] + +[endsect] diff --git a/doc/concepts/Transform.qbk b/doc/concepts/Transform.qbk new file mode 100644 index 00000000..9d13bd5a --- /dev/null +++ b/doc/concepts/Transform.qbk @@ -0,0 +1,63 @@ +[section:Transform Transform] + +A [*Transform] implements a monotonic mapping between two real-valued domains, external and internal. It is used to extend the [classref boost::histogram::axis::regular regular axis]. The bins in the internal domain are of equal width, while the bins in the external domain are non-equal width. Must be [@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]. + +[heading Required features] + +* `T` is a type meeting the requirements of [*Transform] +* `t` is a value of type `T` +* `X` is a type with the semantics of a floating-point type +* `x` is a value of type `X` +* `Y` is a floating-point type +* `y` is a value of type `Y` + +[table Valid expressions +[[Expression] [Return type] [Semantics, Pre/Post-conditions]] +[ + [`t.forward(x)`] + [`Y`] + [ + Const or static member function which maps the external value to the corresponding internal value. The return type `Y` may differ from `X`. + ] +] +[ + [`t.inverse(y)`] + [`X`] + [ + Const or static member function which maps the internal value to the corresponding external value. The result of `t.inverse(t.forward(x))` must be approximately equal to `x` within floating-point precision. + ] +] +[ + [`t == u`] + [`bool`] + [ + `u` is another value of type `T`. Returns `true` if both values have the same state. Otherwise returns `false`. May be omitted if `T` is stateless. If this binary operator is not implemented, the library considers the arguments equal, if and only if their types are the same. + ] +] +] + +[heading Optional features] + +* `T` is a type meeting the requirements of [*Transform] +* `t` is a value of type `T` +* `ar` is a value of an archive with Boost.Serialization semantics + +[table Valid expressions +[[Expression] [Return type] [Semantics, Pre/Post-conditions]] +[ + [`ar & t`] + [] + [ + Serializes `a` to the archive or loads serialized state from the archive. Can be omitted if `T` is stateless. + ] +] +] + +[heading Models] + +* [classref boost::histogram::axis::transform::id] +* [classref boost::histogram::axis::transform::log] +* [classref boost::histogram::axis::transform::sqrt] +* [classref boost::histogram::axis::transform::pow] + +[endsect] diff --git a/doc/guide.qbk b/doc/guide.qbk index 1726a6ce..eaa9f119 100644 --- a/doc/guide.qbk +++ b/doc/guide.qbk @@ -4,7 +4,7 @@ Boost.Histogram is designed to make simple things simple, yet complex things pos [section Make a histogram] -A histogram consists of a [link histogram.concepts.storage storage] and a sequence of [link histogram.concepts.axis axis] objects. The storage represents a grid of cells of counters. The axis objects maps input values to indices, which are used to look up the cell. You don't normally have to worry about the storage, since the library provides a very good default. There are many interesting axis types to choose from, but for now let us stick to the most common axis, the [classref boost::histogram::axis::regular regular] axis. It represents equidistant intervals on the real line. +A histogram consists of a [link histogram.concepts.Storage storage] and a sequence of [link histogram.concepts.Axis axis objects]. The storage represents a grid of cells of counters. The axis objects maps input values to indices, which are used to look up the cell. You don't normally have to worry about the storage, since the library provides a very good default. There are many interesting axis types to choose from, but for now let us stick to the most common axis, the [classref boost::histogram::axis::regular regular] axis. It represents equidistant intervals on the real line. Use the convenient factory function [headerref boost/histogram/make_histogram.hpp make_histogram] to make the histograms. In the following example, a histogram with a single axis is created. @@ -129,7 +129,7 @@ A common need is a regular binning in the logarithm of the input value. This can As shown in the example, due to the finite precision of floating point calculations, the bin edges of a transformed regular axis may not be exactly at the expected values. If you need exact correspondence, use a [classref boost::histogram::axis::variable variable] axis. -Users may write their own transforms and use them with the builtin [classref boost::histogram::axis::regular regular] axis, by implementing a type that matches the [link histogram.concepts.transform transform concept]. +Users may write their own transforms and use them with the builtin [classref boost::histogram::axis::regular regular] axis, by implementing a type that matches the [link histogram.concepts.Transform [*Transform] concept]. [endsect] @@ -319,7 +319,7 @@ The library currently does not provide a builtin thread-safe storage yet (one wi [section User-defined axes] -It is easy to make custom axis classes that work with the library. The custom axis class must meet the requirements of the [link histogram.concepts.axis axis concept]. +It is easy to make custom axis classes that work with the library. The custom axis class must meet the requirements of the [link histogram.concepts.Axis [*Axis] concept]. Users can create a custom axis by derive from a builtin axis type and customize its behavior. The following examples demonstrates a modification of the [classref boost::histogram::axis::integer integer axis]. @@ -380,7 +380,7 @@ The library provides several accumulators: * [classref boost::histogram::accumulators::mean mean] accepts a sample and computes the mean of the samples. [funcref boost::histogram::make_profile make_profile] uses this accumulator. * [classref boost::histogram::accumulators::weighted_mean weighted_mean] accepts a sample and a weight. It computes the weighted mean of the samples. [funcref boost::histogram::make_weighted_profile make_weighted_profile] uses this accumulator. -Users can easily write their own accumulators and plug them into the histogram, if they adhere to the [link histogram.concepts.transform accumulator concept]. +Users can easily write their own accumulators and plug them into the histogram, if they adhere to the [link histogram.concepts.Transform [*Accumulator] concept]. [import ../examples/guide_custom_accumulators.cpp] [guide_custom_accumulators] diff --git a/doc/histogram.qbk b/doc/histogram.qbk index 988f24a2..47675952 100644 --- a/doc/histogram.qbk +++ b/doc/histogram.qbk @@ -87,6 +87,6 @@ Klemens Morgenstern helped to make this library Boost-compliant, converting the Mateusz Loskot kindly agreed to fill the role of the Review Manager for this library and contributed various patches. -Steven Watanabe provided a very detailed review of the documentation and code of the library. Great reviews were submitted by Bjorn Reese, Jim Pivarski, Klemens Morgenstern, and Alex Hagen-Zanker. Comments and suggestions were provided by Andrea Bocci, degksi, Glen Fernandes, Gavin Lambert, Seth, and Mateusz Loskot. +Steven Watanabe provided a very detailed review of the documentation and code of the library. Great reviews were submitted by Bjorn Reese, Jim Pivarski, Klemens Morgenstern, and Alex Hagen-Zanker. Comments and suggestions were provided by Andrea Bocci, degksi, Glen Fernandes, Gavin Lambert, Seth, and Mateusz Loskot. Peter Dimov, Vinnie Falco, and Glen Fernandes were invaluable source of information and feedback on the Cpplang Slack. The members of the [@http://www.scikit-hep.org Scikit-HEP project] provided valuable feedback and input on the design of this library, special thanks go to Henry Schreiner and Jim Pivarski. diff --git a/doc/rationale.qbk b/doc/rationale.qbk index 4590ea12..2e6083ab 100644 --- a/doc/rationale.qbk +++ b/doc/rationale.qbk @@ -52,7 +52,7 @@ The design decision to store axis types in the variant-like type `boost::histogr [section:axis Axis types] -An axis defines an injective mapping of (a range of) input values to a bin. The logic is encapsulated in an axis type. Users can create their own axis classes and use them with the library, by implementing the [link histogram.concepts.axis axis concept]. The library comes with four builtin types, which implement different specializations. +An axis defines an injective mapping of (a range of) input values to a bin. The logic is encapsulated in an axis type. Users can create their own axis classes and use them with the library, by implementing the [link histogram.concepts.Axis [*Axis] concept]. The library comes with four builtin types, which implement different specializations. * [classref boost::histogram::axis::regular] sorts real numbers into bins with equal width. The regular axis also supports monotonic transforms, which are applied when the input values are passed to the axis. This can be used to make a fast logarithmic axis, where the bins have equal width in the logarithm of the variable. * [classref boost::histogram::axis::variable] sorts real numbers into bins with varying width. @@ -70,7 +70,7 @@ Each builtin axis type has a few compile-time options, which change its behavior [section:storage Storage types] -A storage type holds the actual cell values. It uses a one-dimensional index for cell lookup, computed by the histogram host from the indices generated by its axes. The storage needs to know nothing about axes. Users can integrate their own storage classes with the library, by implementing the [link histogram.concepts.storage storage concept]. Standard containers can be used as storage backends, the library adapts them with the [classref boost::histogram::storage_adaptor]. +A storage type holds the actual cell values. It uses a one-dimensional index for cell lookup, computed by the histogram host from the indices generated by its axes. The storage needs to know nothing about axes. Users can integrate their own storage classes with the library, by implementing the [link histogram.concepts.Storage storage concept]. Standard containers can be used as storage backends, the library adapts them with the [classref boost::histogram::storage_adaptor]. Cell lookup is often happening in a tight loop and is random-access. A normal `std::vector` works well as a storage backend. Sometimes this is the best solution, but there are some caveats to this approach. The user has to decide which type should represents the cell counts and it is not an obvious choice. An integer type needs to be large enough to avoid counter overflow, but only a fraction of the bits are used if its capacity is too large. This is a waste of memory then. When memory is wasted, more cache misses occur and performance is degraded (see the benchmarks). The performance of modern CPUs depends a lot on effective utilization of the CPU cache, which is still small. Using floating point numbers instead of integers is also dangerous. They don't overflow, but cap the bin count when the bits in the mantissa are used up. diff --git a/include/boost/histogram/accumulators/ostream.hpp b/include/boost/histogram/accumulators/ostream.hpp index be294d20..b88c133a 100644 --- a/include/boost/histogram/accumulators/ostream.hpp +++ b/include/boost/histogram/accumulators/ostream.hpp @@ -10,6 +10,8 @@ #include #include +#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED + namespace boost { namespace histogram { namespace accumulators { @@ -45,4 +47,6 @@ std::basic_ostream& operator<<(std::basic_ostream& } // namespace histogram } // namespace boost +#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED + #endif diff --git a/include/boost/histogram/axis/category.hpp b/include/boost/histogram/axis/category.hpp index f5d63a3b..bc3ab23a 100644 --- a/include/boost/histogram/axis/category.hpp +++ b/include/boost/histogram/axis/category.hpp @@ -150,7 +150,7 @@ public: return !operator==(o); } - allocator_type get_allocator() const { return vec_meta_.first().get_allocator(); } + auto get_allocator() const { return vec_meta_.first().get_allocator(); } template void serialize(Archive&, unsigned); diff --git a/include/boost/histogram/axis/ostream.hpp b/include/boost/histogram/axis/ostream.hpp index c4537db9..f394e490 100644 --- a/include/boost/histogram/axis/ostream.hpp +++ b/include/boost/histogram/axis/ostream.hpp @@ -21,6 +21,8 @@ #include #include +#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED + namespace boost { namespace histogram { @@ -186,4 +188,6 @@ std::basic_ostream& operator<<(std::basic_ostream& os, } // namespace histogram } // namespace boost +#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED + #endif diff --git a/include/boost/histogram/axis/variable.hpp b/include/boost/histogram/axis/variable.hpp index 8f56a7a5..49cdece5 100644 --- a/include/boost/histogram/axis/variable.hpp +++ b/include/boost/histogram/axis/variable.hpp @@ -192,7 +192,7 @@ public: } /// Return allocator instance. - allocator_type get_allocator() const { return vec_meta_.first().get_allocator(); } + auto get_allocator() const { return vec_meta_.first().get_allocator(); } template void serialize(Archive&, unsigned); diff --git a/include/boost/histogram/detail/meta.hpp b/include/boost/histogram/detail/meta.hpp index bc0ce9ab..dfd9e66d 100644 --- a/include/boost/histogram/detail/meta.hpp +++ b/include/boost/histogram/detail/meta.hpp @@ -134,12 +134,6 @@ decltype(auto) tuple_slice(T&& t) { return tuple_slice_impl(std::forward(t), mp11::make_index_sequence{}); } -template -using get_storage_tag = typename T::storage_tag; - -template -using is_storage = mp11::mp_valid; - #define BOOST_HISTOGRAM_DETECT(name, cond) \ template \ struct name##_impl {}; \ @@ -167,6 +161,8 @@ BOOST_HISTOGRAM_DETECT(has_method_value, &T::value); BOOST_HISTOGRAM_DETECT(has_method_update, (&T::update)); +BOOST_HISTOGRAM_DETECT(has_method_reset, (std::declval().reset(0))); + template using get_value_method_return_type_impl = decltype(std::declval().value(0)); @@ -183,20 +179,22 @@ BOOST_HISTOGRAM_DETECT(is_indexable, (std::declval()[0])); BOOST_HISTOGRAM_DETECT(is_transform, (&T::forward, &T::inverse)); -BOOST_HISTOGRAM_DETECT(is_vector_like, - (std::declval()[0], &T::size, std::declval().resize(0), - &T::cbegin, &T::cend)); +BOOST_HISTOGRAM_DETECT(is_indexable_container, + (std::declval()[0], &T::size, std::begin(std::declval()), + std::end(std::declval()))); -BOOST_HISTOGRAM_DETECT(is_array_like, (std::declval()[0], &T::size, - std::tuple_size::value, &T::cbegin, &T::cend)); +BOOST_HISTOGRAM_DETECT(is_vector_like, + (std::declval()[0], &T::size, std::declval().resize(0), + std::begin(std::declval()), std::end(std::declval()))); + +BOOST_HISTOGRAM_DETECT(is_array_like, + (std::declval()[0], &T::size, std::tuple_size::value, + std::begin(std::declval()), std::end(std::declval()))); BOOST_HISTOGRAM_DETECT(is_map_like, - (typename T::key_type(), typename T::mapped_type(), - std::declval().begin(), std::declval().end())); - -BOOST_HISTOGRAM_DETECT(is_indexable_container, - (std::declval()[0], &T::size, std::begin(std::declval()), - std::end(std::declval()))); + (std::declval(), + std::declval(), + std::begin(std::declval()), std::end(std::declval()))); // ok: is_axis is false for axis::variant, operator() is templated BOOST_HISTOGRAM_DETECT(is_axis, (&T::size, &T::index)); @@ -212,8 +210,6 @@ BOOST_HISTOGRAM_DETECT(is_streamable, BOOST_HISTOGRAM_DETECT(is_incrementable, (++std::declval())); -BOOST_HISTOGRAM_DETECT(has_fixed_size, (std::tuple_size::value)); - BOOST_HISTOGRAM_DETECT(has_operator_preincrement, (++std::declval())); BOOST_HISTOGRAM_DETECT_BINARY(has_operator_equal, @@ -231,6 +227,10 @@ BOOST_HISTOGRAM_DETECT_BINARY(has_operator_rmul, BOOST_HISTOGRAM_DETECT_BINARY(has_operator_rdiv, (std::declval() /= std::declval())); +template +using is_storage = + mp11::mp_bool<(is_indexable_container::value && has_method_reset::value)>; + template struct is_tuple_impl : std::false_type {}; @@ -323,7 +323,7 @@ template using tuple_size_t = typename std::tuple_size::type; template -std::size_t get_size_impl(std::true_type, const T&) noexcept { +constexpr std::size_t get_size_impl(std::true_type, const T&) noexcept { return std::tuple_size::value; } diff --git a/include/boost/histogram/fwd.hpp b/include/boost/histogram/fwd.hpp index 34ad06d9..d9b77b73 100644 --- a/include/boost/histogram/fwd.hpp +++ b/include/boost/histogram/fwd.hpp @@ -9,7 +9,7 @@ /** \file boost/histogram/fwd.hpp - Forward declarations, basic typedefs, and default template arguments for main classes. + Forward declarations, tag types and type aliases. */ #include @@ -20,7 +20,7 @@ namespace boost { namespace histogram { -/// tag type to indicate use of a default type +/// Tag type to indicate use of a default type using boost::use_default; namespace axis { @@ -31,9 +31,11 @@ using index_type = int; /// Real type for axis indices using real_index_type = double; -/// empty metadata type +/// Empty metadata type struct null_type {}; +#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED + namespace transform { struct id; struct log; @@ -58,8 +60,12 @@ class category; template class variant; + +#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED + } // namespace axis +#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED template struct weight_type; @@ -85,19 +91,28 @@ class unlimited_storage; template class storage_adaptor; +#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED + +/// Vector-like storage for fast zero-overhead access to cells template > using dense_storage = storage_adaptor>; +/// Default storage, optimized for unweighted histograms using default_storage = unlimited_storage<>; +/// Dense storage which tracks sums of weights and a variance estimate using weight_storage = dense_storage>; +/// Dense storage which tracks means of samples in each cell using profile_storage = dense_storage>; +/// Dense storage which tracks means of weighted samples in each cell using weighted_profile_storage = dense_storage>; +#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED template class BOOST_HISTOGRAM_DETAIL_NODISCARD histogram; +#endif } // namespace histogram } // namespace boost diff --git a/include/boost/histogram/make_profile.hpp b/include/boost/histogram/make_profile.hpp index b2d1eaa0..c9529c4b 100644 --- a/include/boost/histogram/make_profile.hpp +++ b/include/boost/histogram/make_profile.hpp @@ -50,8 +50,8 @@ auto make_weighted_profile(Axis&& axis, Axes&&... axes) { @param iterable Iterable range of axis objects. */ template > -auto make_profile(Iterable&& c) { - return make_histogram_with(profile_storage(), std::forward(c)); +auto make_profile(Iterable&& iterable) { + return make_histogram_with(profile_storage(), std::forward(iterable)); } /** @@ -59,8 +59,9 @@ auto make_profile(Iterable&& c) { @param iterable Iterable range of axis objects. */ template > -auto make_weighted_profile(Iterable&& c) { - return make_histogram_with(weighted_profile_storage(), std::forward(c)); +auto make_weighted_profile(Iterable&& iterable) { + return make_histogram_with(weighted_profile_storage(), + std::forward(iterable)); } /** diff --git a/include/boost/histogram/ostream.hpp b/include/boost/histogram/ostream.hpp index 56db2082..bef4a78e 100644 --- a/include/boost/histogram/ostream.hpp +++ b/include/boost/histogram/ostream.hpp @@ -12,6 +12,8 @@ #include #include +#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED + namespace boost { namespace histogram { @@ -32,4 +34,6 @@ std::basic_ostream& operator<<(std::basic_ostream& } // namespace histogram } // namespace boost +#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED + #endif diff --git a/include/boost/histogram/unlimited_storage.hpp b/include/boost/histogram/unlimited_storage.hpp index 489d7dcf..9b63dcb2 100644 --- a/include/boost/histogram/unlimited_storage.hpp +++ b/include/boost/histogram/unlimited_storage.hpp @@ -337,7 +337,6 @@ class unlimited_storage { "unlimited_storage requires allocator with trivial pointer type"); public: - struct storage_tag {}; using allocator_type = Allocator; using value_type = double; using mp_int = detail::mp_int< diff --git a/test/meta_test.cpp b/test/meta_test.cpp index 0df9e662..d6203777 100644 --- a/test/meta_test.cpp +++ b/test/meta_test.cpp @@ -140,10 +140,12 @@ int main() { // is_storage { struct A {}; - using B = unlimited_storage<>; + using B = std::vector; + using C = unlimited_storage<>; BOOST_TEST_TRAIT_FALSE((is_storage)); - BOOST_TEST_TRAIT_TRUE((is_storage)); + BOOST_TEST_TRAIT_FALSE((is_storage)); + BOOST_TEST_TRAIT_TRUE((is_storage)); } // is_indexable @@ -383,19 +385,6 @@ int main() { BOOST_TEST_TRAIT_TRUE((is_sample)); } - // has_fixed_size - { - struct A {}; - using B = std::vector; - using C = std::tuple; - using D = std::array; - - BOOST_TEST_NOT(has_fixed_size::value); - BOOST_TEST_NOT(has_fixed_size::value); - BOOST_TEST(has_fixed_size::value); - BOOST_TEST(has_fixed_size::value); - } - // make_default { struct A {}; diff --git a/test/utility_meta.hpp b/test/utility_meta.hpp index f5e60425..22cdc3b3 100644 --- a/test/utility_meta.hpp +++ b/test/utility_meta.hpp @@ -26,14 +26,19 @@ ostream& operator<<(ostream& os, const vector& v) { return os; } -template ::value>> -ostream& operator<<(ostream& os, const T& t) { +template +ostream& operator<<(ostream& os, const std::tuple& t) { os << "[ "; ::boost::mp11::tuple_for_each(t, [&os](const auto& x) { os << x << " "; }); os << "]"; return os; } + +template +ostream& operator<<(ostream& os, const std::pair& t) { + os << "[ " << t.first << " " << t.second << " ]"; + return os; +} } // namespace std #ifndef BOOST_TEST_TRAIT_SAME