Imported Upstream version 1.72.0
[platform/upstream/boost.git] / boost / histogram / histogram.hpp
index 75bc0df..d5fa187 100644 (file)
@@ -7,16 +7,24 @@
 #ifndef BOOST_HISTOGRAM_HISTOGRAM_HPP
 #define BOOST_HISTOGRAM_HISTOGRAM_HPP
 
+#include <boost/histogram/detail/accumulator_traits.hpp>
+#include <boost/histogram/detail/argument_traits.hpp>
+#include <boost/histogram/detail/at.hpp>
 #include <boost/histogram/detail/axes.hpp>
 #include <boost/histogram/detail/common_type.hpp>
-#include <boost/histogram/detail/compressed_pair.hpp>
-#include <boost/histogram/detail/linearize.hpp>
-#include <boost/histogram/detail/noop_mutex.hpp>
-#include <boost/histogram/detail/static_if.hpp>
+#include <boost/histogram/detail/fill.hpp>
+#include <boost/histogram/detail/fill_n.hpp>
+#include <boost/histogram/detail/mutex_base.hpp>
+#include <boost/histogram/detail/non_member_container_access.hpp>
+#include <boost/histogram/detail/span.hpp>
 #include <boost/histogram/fwd.hpp>
+#include <boost/histogram/sample.hpp>
 #include <boost/histogram/storage_adaptor.hpp>
 #include <boost/histogram/unsafe_access.hpp>
+#include <boost/histogram/weight.hpp>
+#include <boost/mp11/integral.hpp>
 #include <boost/mp11/list.hpp>
+#include <boost/mp11/tuple.hpp>
 #include <boost/throw_exception.hpp>
 #include <mutex>
 #include <stdexcept>
@@ -47,11 +55,10 @@ namespace histogram {
   @tparam Storage class that implements the storage interface
  */
 template <class Axes, class Storage>
-class histogram {
-public:
-  static_assert(mp11::mp_size<Axes>::value > 0, "at least one axis required");
+class histogram : detail::mutex_base<Axes, Storage> {
   static_assert(std::is_same<std::decay_t<Storage>, Storage>::value,
                 "Storage may not be a reference or const or volatile");
+  static_assert(mp11::mp_size<Axes>::value > 0, "at least one axis required");
 
 public:
   using axes_type = Axes;
@@ -61,70 +68,69 @@ public:
   using iterator = typename storage_type::iterator;
   using const_iterator = typename storage_type::const_iterator;
 
+private:
+  using mutex_base = typename detail::mutex_base<axes_type, storage_type>;
+
+public:
   histogram() = default;
-  histogram(const histogram& rhs)
-      : axes_(rhs.axes_), storage_and_mutex_(rhs.storage_and_mutex_.first()) {}
-  histogram(histogram&& rhs)
-      : axes_(std::move(rhs.axes_))
-      , storage_and_mutex_(std::move(rhs.storage_and_mutex_.first())) {}
-  histogram& operator=(histogram&& rhs) {
-    if (this != &rhs) {
-      axes_ = std::move(rhs.axes_);
-      storage_and_mutex_.first() = std::move(rhs.storage_and_mutex_.first());
-    }
-    return *this;
-  }
-  histogram& operator=(const histogram& rhs) {
-    if (this != &rhs) {
-      axes_ = rhs.axes_;
-      storage_and_mutex_.first() = rhs.storage_and_mutex_.first();
-    }
-    return *this;
-  }
 
   template <class A, class S>
   explicit histogram(histogram<A, S>&& rhs)
-      : storage_and_mutex_(std::move(unsafe_access::storage(rhs))) {
+      : storage_(std::move(unsafe_access::storage(rhs)))
+      , offset_(unsafe_access::offset(rhs)) {
     detail::axes_assign(axes_, std::move(unsafe_access::axes(rhs)));
+    detail::throw_if_axes_is_too_large(axes_);
   }
 
   template <class A, class S>
   explicit histogram(const histogram<A, S>& rhs)
-      : storage_and_mutex_(unsafe_access::storage(rhs)) {
+      : storage_(unsafe_access::storage(rhs)), offset_(unsafe_access::offset(rhs)) {
     detail::axes_assign(axes_, unsafe_access::axes(rhs));
+    detail::throw_if_axes_is_too_large(axes_);
   }
 
   template <class A, class S>
   histogram& operator=(histogram<A, S>&& rhs) {
     detail::axes_assign(axes_, std::move(unsafe_access::axes(rhs)));
-    storage_and_mutex_.first() = std::move(unsafe_access::storage(rhs));
+    detail::throw_if_axes_is_too_large(axes_);
+    storage_ = std::move(unsafe_access::storage(rhs));
+    offset_ = unsafe_access::offset(rhs);
     return *this;
   }
 
   template <class A, class S>
   histogram& operator=(const histogram<A, S>& rhs) {
     detail::axes_assign(axes_, unsafe_access::axes(rhs));
-    storage_and_mutex_.first() = unsafe_access::storage(rhs);
+    detail::throw_if_axes_is_too_large(axes_);
+    storage_ = unsafe_access::storage(rhs);
+    offset_ = unsafe_access::offset(rhs);
     return *this;
   }
 
-  template <class A, class S>
-  histogram(A&& a, S&& s)
-      : axes_(std::forward<A>(a)), storage_and_mutex_(std::forward<S>(s)) {
-    storage_and_mutex_.first().reset(detail::bincount(axes_));
+  template <class A, class = detail::requires_axes<A>>
+  histogram(A&& a, Storage s)
+      : axes_(std::forward<A>(a))
+      , storage_(std::move(s))
+      , offset_(detail::offset(axes_)) {
+    detail::throw_if_axes_is_too_large(axes_);
+    storage_.reset(detail::bincount(axes_));
   }
 
-  template <class A, class = detail::requires_axes<A>>
-  explicit histogram(A&& a) : histogram(std::forward<A>(a), storage_type()) {}
+  explicit histogram(Axes axes) : histogram(axes, storage_type()) {}
+
+  template <class... As, class = detail::requires_axes<std::tuple<std::decay_t<As>...>>>
+  explicit histogram(As&&... as)
+      : histogram(std::tuple<std::decay_t<As>...>(std::forward<As>(as)...),
+                  storage_type()) {}
 
   /// Number of axes (dimensions).
   constexpr unsigned rank() const noexcept { return detail::axes_rank(axes_); }
 
   /// Total number of bins (including underflow/overflow).
-  std::size_t size() const noexcept { return storage_and_mutex_.first().size(); }
+  std::size_t size() const noexcept { return storage_.size(); }
 
   /// Reset all bins to default initialized values.
-  void reset() { storage_and_mutex_.first().reset(size()); }
+  void reset() { storage_.reset(size()); }
 
   /// Get N-th axis using a compile-time number.
   /// This version is more efficient than the one accepting a run-time number.
@@ -149,41 +155,184 @@ public:
 
   /** Fill histogram with values, an optional weight, and/or a sample.
 
-   Arguments are passed in order to the axis objects. Passing an argument type that is
-   not convertible to the value type accepted by the axis or passing the wrong number
-   of arguments causes a throw of `std::invalid_argument`.
+    Arguments are passed in order to the axis objects. Passing an argument type that is
+    not convertible to the value type accepted by the axis or passing the wrong number
+    of arguments causes a throw of `std::invalid_argument`.
 
-   __Optional weight__
+    __Optional weight__
 
-   An optional weight can be passed as the first or last argument
-   with the [weight](boost/histogram/weight.html) helper function. Compilation fails if
-   the storage elements do not support weights.
+    An optional weight can be passed as the first or last argument
+    with the [weight](boost/histogram/weight.html) helper function. Compilation fails if
+    the storage elements do not support weights.
 
-   __Samples__
+    __Samples__
 
-   If the storage elements accept samples, pass them with the sample helper function
-   in addition to the axis arguments, which can be the first or last argument. The
-   [sample](boost/histogram/sample.html) helper function can pass one or more arguments to
-   the storage element. If samples and weights are used together, they can be passed in
-   any order at the beginning or end of the argument list.
+    If the storage elements accept samples, pass them with the sample helper function
+    in addition to the axis arguments, which can be the first or last argument. The
+    [sample](boost/histogram/sample.html) helper function can pass one or more arguments
+    to the storage element. If samples and weights are used together, they can be passed
+    in any order at the beginning or end of the argument list.
 
-   __Axis with multiple arguments__
+    __Axis with multiple arguments__
 
-   If the histogram contains an axis which accepts a `std::tuple` of arguments, the
-   arguments for that axis need to passed as a `std::tuple`, for example,
-   `std::make_tuple(1.2, 2.3)`. If the histogram contains only this axis and no other,
-   the arguments can be passed directly.
+    If the histogram contains an axis which accepts a `std::tuple` of arguments, the
+    arguments for that axis need to passed as a `std::tuple`, for example,
+    `std::make_tuple(1.2, 2.3)`. If the histogram contains only this axis and no other,
+    the arguments can be passed directly.
   */
-  template <class... Ts>
-  iterator operator()(const Ts&... ts) {
-    return operator()(std::forward_as_tuple(ts...));
+  template <class Arg0, class... Args>
+  std::enable_if_t<(detail::is_tuple<Arg0>::value == false || sizeof...(Args) > 0),
+                   iterator>
+  operator()(const Arg0& arg0, const Args&... args) {
+    return operator()(std::forward_as_tuple(arg0, args...));
   }
 
   /// Fill histogram with values, an optional weight, and/or a sample from a `std::tuple`.
   template <class... Ts>
-  iterator operator()(const std::tuple<Ts...>& t) {
-    std::lock_guard<mutex_type> guard{storage_and_mutex_.second()};
-    return detail::fill(axes_, storage_and_mutex_.first(), t);
+  iterator operator()(const std::tuple<Ts...>& args) {
+    using arg_traits = detail::argument_traits<std::decay_t<Ts>...>;
+    using acc_traits = detail::accumulator_traits<value_type>;
+    constexpr bool weight_valid =
+        arg_traits::wpos::value == -1 || acc_traits::wsupport::value;
+    static_assert(weight_valid, "error: accumulator does not support weights");
+    detail::sample_args_passed_vs_expected<typename arg_traits::sargs,
+                                           typename acc_traits::args>();
+    constexpr bool sample_valid =
+        std::is_convertible<typename arg_traits::sargs, typename acc_traits::args>::value;
+    std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
+    return detail::fill(mp11::mp_bool<(weight_valid && sample_valid)>{}, arg_traits{},
+                        offset_, storage_, axes_, args);
+  }
+
+  /** Fill histogram with several values at once.
+
+    The argument must be an iterable with a size that matches the
+    rank of the histogram. The element of an iterable may be 1) a value or 2) an iterable
+    with contiguous storage over values or 3) a variant of 1) and 2). Sub-iterables must
+    have the same length.
+
+    Values are passed to the corresponding histogram axis in order. If a single value is
+    passed together with an iterable of values, the single value is treated like an
+    iterable with matching length of copies of this value.
+
+    If the histogram has only one axis, an iterable of values may be passed directly.
+
+    @param args iterable as explained in the long description.
+  */
+  template <class Iterable, class = detail::requires_iterable<Iterable>>
+  void fill(const Iterable& args) {
+    using acc_traits = detail::accumulator_traits<value_type>;
+    constexpr unsigned n_sample_args_expected =
+        std::tuple_size<typename acc_traits::args>::value;
+    static_assert(n_sample_args_expected == 0,
+                  "sample argument is missing but required by accumulator");
+    std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
+    detail::fill_n(mp11::mp_bool<(n_sample_args_expected == 0)>{}, offset_, storage_,
+                   axes_, detail::make_span(args));
+  }
+
+  /** Fill histogram with several values and weights at once.
+
+    @param args iterable of values.
+    @param weights single weight or an iterable of weights.
+  */
+  template <class Iterable, class T, class = detail::requires_iterable<Iterable>>
+  void fill(const Iterable& args, const weight_type<T>& weights) {
+    using acc_traits = detail::accumulator_traits<value_type>;
+    constexpr bool weight_valid = acc_traits::wsupport::value;
+    static_assert(weight_valid, "error: accumulator does not support weights");
+    detail::sample_args_passed_vs_expected<std::tuple<>, typename acc_traits::args>();
+    constexpr bool sample_valid =
+        std::is_convertible<std::tuple<>, typename acc_traits::args>::value;
+    std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
+    detail::fill_n(mp11::mp_bool<(weight_valid && sample_valid)>{}, offset_, storage_,
+                   axes_, detail::make_span(args),
+                   weight(detail::to_ptr_size(weights.value)));
+  }
+
+  /** Fill histogram with several values and weights at once.
+
+    @param weights single weight or an iterable of weights.
+    @param args iterable of values.
+  */
+  template <class Iterable, class T, class = detail::requires_iterable<Iterable>>
+  void fill(const weight_type<T>& weights, const Iterable& args) {
+    fill(args, weights);
+  }
+
+  /** Fill histogram with several values and samples at once.
+
+    @param args iterable of values.
+    @param samples single sample or an iterable of samples.
+  */
+  template <class Iterable, class... Ts, class = detail::requires_iterable<Iterable>>
+  void fill(const Iterable& args, const sample_type<std::tuple<Ts...>>& samples) {
+    using acc_traits = detail::accumulator_traits<value_type>;
+    using sample_args_passed =
+        std::tuple<decltype(*detail::to_ptr_size(std::declval<Ts>()).first)...>;
+    detail::sample_args_passed_vs_expected<sample_args_passed,
+                                           typename acc_traits::args>();
+    std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
+    mp11::tuple_apply(
+        [&](const auto&... sargs) {
+          constexpr bool sample_valid =
+              std::is_convertible<sample_args_passed, typename acc_traits::args>::value;
+          detail::fill_n(mp11::mp_bool<(sample_valid)>{}, offset_, storage_, axes_,
+                         detail::make_span(args), detail::to_ptr_size(sargs)...);
+        },
+        samples.value);
+  }
+
+  /** Fill histogram with several values and samples at once.
+
+    @param samples single sample or an iterable of samples.
+    @param args iterable of values.
+  */
+  template <class Iterable, class T, class = detail::requires_iterable<Iterable>>
+  void fill(const sample_type<T>& samples, const Iterable& args) {
+    fill(args, samples);
+  }
+
+  template <class Iterable, class T, class... Ts,
+            class = detail::requires_iterable<Iterable>>
+  void fill(const Iterable& args, const weight_type<T>& weights,
+            const sample_type<std::tuple<Ts...>>& samples) {
+    using acc_traits = detail::accumulator_traits<value_type>;
+    using sample_args_passed =
+        std::tuple<decltype(*detail::to_ptr_size(std::declval<Ts>()).first)...>;
+    detail::sample_args_passed_vs_expected<sample_args_passed,
+                                           typename acc_traits::args>();
+    std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
+    mp11::tuple_apply(
+        [&](const auto&... sargs) {
+          constexpr bool weight_valid = acc_traits::wsupport::value;
+          static_assert(weight_valid, "error: accumulator does not support weights");
+          constexpr bool sample_valid =
+              std::is_convertible<sample_args_passed, typename acc_traits::args>::value;
+          detail::fill_n(mp11::mp_bool<(weight_valid && sample_valid)>{}, offset_,
+                         storage_, axes_, detail::make_span(args),
+                         weight(detail::to_ptr_size(weights.value)),
+                         detail::to_ptr_size(sargs)...);
+        },
+        samples.value);
+  }
+
+  template <class Iterable, class T, class U, class = detail::requires_iterable<Iterable>>
+  void fill(const sample_type<T>& samples, const weight_type<U>& weights,
+            const Iterable& args) {
+    fill(args, weights, samples);
+  }
+
+  template <class Iterable, class T, class U, class = detail::requires_iterable<Iterable>>
+  void fill(const weight_type<T>& weights, const sample_type<U>& samples,
+            const Iterable& args) {
+    fill(args, weights, samples);
+  }
+
+  template <class Iterable, class T, class U, class = detail::requires_iterable<Iterable>>
+  void fill(const Iterable& args, const sample_type<T>& samples,
+            const weight_type<U>& weights) {
+    fill(args, weights, samples);
   }
 
   /** Access cell value at integral indices.
@@ -209,39 +358,55 @@ public:
   }
 
   /// Access cell value at integral indices stored in `std::tuple`.
-  template <typename... Indices>
+  template <class... Indices>
   decltype(auto) at(const std::tuple<Indices...>& is) {
+    if (rank() != sizeof...(Indices))
+      BOOST_THROW_EXCEPTION(
+          std::invalid_argument("number of arguments != histogram rank"));
     const auto idx = detail::at(axes_, is);
-    if (!idx)
+    if (!is_valid(idx))
       BOOST_THROW_EXCEPTION(std::out_of_range("at least one index out of bounds"));
-    return storage_and_mutex_.first()[*idx];
+    BOOST_ASSERT(idx < storage_.size());
+    return storage_[idx];
   }
 
   /// Access cell value at integral indices stored in `std::tuple` (read-only).
   template <typename... Indices>
   decltype(auto) at(const std::tuple<Indices...>& is) const {
+    if (rank() != sizeof...(Indices))
+      BOOST_THROW_EXCEPTION(
+          std::invalid_argument("number of arguments != histogram rank"));
     const auto idx = detail::at(axes_, is);
-    if (!idx)
+    if (!is_valid(idx))
       BOOST_THROW_EXCEPTION(std::out_of_range("at least one index out of bounds"));
-    return storage_and_mutex_.first()[*idx];
+    BOOST_ASSERT(idx < storage_.size());
+    return storage_[idx];
   }
 
   /// Access cell value at integral indices stored in iterable.
   template <class Iterable, class = detail::requires_iterable<Iterable>>
   decltype(auto) at(const Iterable& is) {
+    if (rank() != detail::axes_rank(is))
+      BOOST_THROW_EXCEPTION(
+          std::invalid_argument("number of arguments != histogram rank"));
     const auto idx = detail::at(axes_, is);
-    if (!idx)
+    if (!is_valid(idx))
       BOOST_THROW_EXCEPTION(std::out_of_range("at least one index out of bounds"));
-    return storage_and_mutex_.first()[*idx];
+    BOOST_ASSERT(idx < storage_.size());
+    return storage_[idx];
   }
 
   /// Access cell value at integral indices stored in iterable (read-only).
   template <class Iterable, class = detail::requires_iterable<Iterable>>
   decltype(auto) at(const Iterable& is) const {
+    if (rank() != detail::axes_rank(is))
+      BOOST_THROW_EXCEPTION(
+          std::invalid_argument("number of arguments != histogram rank"));
     const auto idx = detail::at(axes_, is);
-    if (!idx)
+    if (!is_valid(idx))
       BOOST_THROW_EXCEPTION(std::out_of_range("at least one index out of bounds"));
-    return storage_and_mutex_.first()[*idx];
+    BOOST_ASSERT(idx < storage_.size());
+    return storage_[idx];
   }
 
   /// Access value at index (number for rank = 1, else `std::tuple` or iterable).
@@ -259,8 +424,10 @@ public:
   /// Equality operator, tests equality for all axes and the storage.
   template <class A, class S>
   bool operator==(const histogram<A, S>& rhs) const noexcept {
-    return detail::axes_equal(axes_, unsafe_access::axes(rhs)) &&
-           storage_and_mutex_.first() == unsafe_access::storage(rhs);
+    // testing offset is redundant, but offers fast return if it fails
+    return offset_ == unsafe_access::offset(rhs) &&
+           detail::axes_equal(axes_, unsafe_access::axes(rhs)) &&
+           storage_ == unsafe_access::storage(rhs);
   }
 
   /// Negation of the equality operator.
@@ -270,89 +437,97 @@ public:
   }
 
   /// Add values of another histogram.
-  template <class A, class S,
-            class = std::enable_if_t<detail::has_operator_radd<
-                value_type, typename histogram<A, S>::value_type>::value>>
-  histogram& operator+=(const histogram<A, S>& rhs) {
+  template <class A, class S>
+  std::enable_if_t<
+      detail::has_operator_radd<value_type, typename histogram<A, S>::value_type>::value,
+      histogram&>
+  operator+=(const histogram<A, S>& rhs) {
     if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
       BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
     auto rit = unsafe_access::storage(rhs).begin();
-    auto& s = storage_and_mutex_.first();
-    std::for_each(s.begin(), s.end(), [&rit](auto&& x) { x += *rit++; });
+    for (auto&& x : storage_) x += *rit++;
     return *this;
   }
 
   /// Subtract values of another histogram.
-  template <class A, class S,
-            class = std::enable_if_t<detail::has_operator_rsub<
-                value_type, typename histogram<A, S>::value_type>::value>>
-  histogram& operator-=(const histogram<A, S>& rhs) {
+  template <class A, class S>
+  std::enable_if_t<
+      detail::has_operator_rsub<value_type, typename histogram<A, S>::value_type>::value,
+      histogram&>
+  operator-=(const histogram<A, S>& rhs) {
     if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
       BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
     auto rit = unsafe_access::storage(rhs).begin();
-    auto& s = storage_and_mutex_.first();
-    std::for_each(s.begin(), s.end(), [&rit](auto&& x) { x -= *rit++; });
+    for (auto&& x : storage_) x -= *rit++;
     return *this;
   }
 
   /// Multiply by values of another histogram.
-  template <class A, class S,
-            class = std::enable_if_t<detail::has_operator_rmul<
-                value_type, typename histogram<A, S>::value_type>::value>>
-  histogram& operator*=(const histogram<A, S>& rhs) {
+  template <class A, class S>
+  std::enable_if_t<
+      detail::has_operator_rmul<value_type, typename histogram<A, S>::value_type>::value,
+      histogram&>
+  operator*=(const histogram<A, S>& rhs) {
     if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
       BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
     auto rit = unsafe_access::storage(rhs).begin();
-    auto& s = storage_and_mutex_.first();
-    std::for_each(s.begin(), s.end(), [&rit](auto&& x) { x *= *rit++; });
+    for (auto&& x : storage_) x *= *rit++;
     return *this;
   }
 
   /// Divide by values of another histogram.
-  template <class A, class S,
-            class = std::enable_if_t<detail::has_operator_rdiv<
-                value_type, typename histogram<A, S>::value_type>::value>>
-  histogram& operator/=(const histogram<A, S>& rhs) {
+  template <class A, class S>
+  std::enable_if_t<
+      detail::has_operator_rdiv<value_type, typename histogram<A, S>::value_type>::value,
+      histogram&>
+  operator/=(const histogram<A, S>& rhs) {
     if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
       BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
     auto rit = unsafe_access::storage(rhs).begin();
-    auto& s = storage_and_mutex_.first();
-    std::for_each(s.begin(), s.end(), [&rit](auto&& x) { x /= *rit++; });
+    for (auto&& x : storage_) x /= *rit++;
     return *this;
   }
 
   /// Multiply all values with a scalar.
-  template <class V = value_type,
-            class = std::enable_if_t<detail::has_operator_rmul<V, double>::value>>
-  histogram& operator*=(const double x) {
+  template <class V = value_type>
+  std::enable_if_t<(detail::has_operator_rmul<V, double>::value &&
+                    detail::has_operator_rmul<storage_type, double>::value == true),
+                   histogram&>
+  operator*=(const double x) {
     // use special implementation of scaling if available
-    detail::static_if<detail::has_operator_rmul<storage_type, double>>(
-        [](storage_type& s, auto x) { s *= x; },
-        [](storage_type& s, auto x) {
-          for (auto&& si : s) si *= x;
-        },
-        storage_and_mutex_.first(), x);
+    storage_ *= x;
+    return *this;
+  }
+
+  /// Multiply all values with a scalar.
+  template <class V = value_type>
+  std::enable_if_t<(detail::has_operator_rmul<V, double>::value &&
+                    detail::has_operator_rmul<storage_type, double>::value == false),
+                   histogram&>
+  operator*=(const double x) {
+    // generic implementation of scaling
+    for (auto&& si : storage_) si *= x;
     return *this;
   }
 
   /// Divide all values by a scalar.
-  template <class V = value_type,
-            class = std::enable_if_t<detail::has_operator_rmul<V, double>::value>>
-  histogram& operator/=(const double x) {
+  template <class V = value_type>
+  std::enable_if_t<detail::has_operator_rmul<V, double>::value, histogram&> operator/=(
+      const double x) {
     return operator*=(1.0 / x);
   }
 
   /// Return value iterator to the beginning of the histogram.
-  iterator begin() noexcept { return storage_and_mutex_.first().begin(); }
+  iterator begin() noexcept { return storage_.begin(); }
 
   /// Return value iterator to the end in the histogram.
-  iterator end() noexcept { return storage_and_mutex_.first().end(); }
+  iterator end() noexcept { return storage_.end(); }
 
   /// Return value iterator to the beginning of the histogram (read-only).
-  const_iterator begin() const noexcept { return storage_and_mutex_.first().begin(); }
+  const_iterator begin() const noexcept { return storage_.begin(); }
 
   /// Return value iterator to the end in the histogram (read-only).
-  const_iterator end() const noexcept { return storage_and_mutex_.first().end(); }
+  const_iterator end() const noexcept { return storage_.end(); }
 
   /// Return value iterator to the beginning of the histogram (read-only).
   const_iterator cbegin() const noexcept { return begin(); }
@@ -360,14 +535,20 @@ public:
   /// Return value iterator to the end in the histogram (read-only).
   const_iterator cend() const noexcept { return end(); }
 
+  template <class Archive>
+  void serialize(Archive& ar, unsigned /* version */) {
+    detail::axes_serialize(ar, axes_);
+    ar& make_nvp("storage", storage_);
+    if (Archive::is_loading::value) {
+      offset_ = detail::offset(axes_);
+      detail::throw_if_axes_is_too_large(axes_);
+    }
+  }
+
 private:
   axes_type axes_;
-
-  using mutex_type = mp11::mp_if_c<(storage_type::has_threading_support &&
-                                    detail::has_growing_axis<axes_type>::value),
-                                   std::mutex, detail::noop_mutex>;
-
-  detail::compressed_pair<storage_type, mutex_type> storage_and_mutex_;
+  storage_type storage_;
+  std::size_t offset_ = 0;
 
   friend struct unsafe_access;
 };
@@ -445,42 +626,27 @@ auto operator/(const histogram<A, S>& h, double x) {
 
 #if __cpp_deduction_guides >= 201606
 
-template <class Axes>
-histogram(Axes&& axes)->histogram<std::decay_t<Axes>, default_storage>;
-
-template <class Axes, class Storage>
-histogram(Axes&& axes, Storage&& storage)
-    ->histogram<std::decay_t<Axes>, std::decay_t<Storage>>;
-
-#endif
-
-/** Helper function to mark argument as weight.
-
-  @param t argument to be forward to the histogram.
-*/
-template <typename T>
-auto weight(T&& t) noexcept {
-  return weight_type<T>{std::forward<T>(t)};
-}
+template <class... Axes, class = detail::requires_axes<std::tuple<std::decay_t<Axes>...>>>
+histogram(Axes...)->histogram<std::tuple<std::decay_t<Axes>...>>;
 
-/** Helper function to mark arguments as sample.
+template <class... Axes, class S, class = detail::requires_storage_or_adaptible<S>>
+histogram(std::tuple<Axes...>, S)
+    ->histogram<std::tuple<Axes...>, std::conditional_t<detail::is_adaptible<S>::value,
+                                                        storage_adaptor<S>, S>>;
 
-  @param ts arguments to be forwarded to the accumulator.
-*/
-template <typename... Ts>
-auto sample(Ts&&... ts) noexcept {
-  return sample_type<std::tuple<Ts...>>{std::forward_as_tuple(std::forward<Ts>(ts)...)};
-}
+template <class Iterable, class = detail::requires_iterable<Iterable>,
+          class = detail::requires_any_axis<typename Iterable::value_type>>
+histogram(Iterable)->histogram<std::vector<typename Iterable::value_type>>;
 
-template <class T>
-struct weight_type {
-  T value;
-};
+template <class Iterable, class S, class = detail::requires_iterable<Iterable>,
+          class = detail::requires_any_axis<typename Iterable::value_type>,
+          class = detail::requires_storage_or_adaptible<S>>
+histogram(Iterable, S)
+    ->histogram<
+        std::vector<typename Iterable::value_type>,
+        std::conditional_t<detail::is_adaptible<S>::value, storage_adaptor<S>, S>>;
 
-template <class T>
-struct sample_type {
-  T value;
-};
+#endif
 
 } // namespace histogram
 } // namespace boost