boost/histogram/unlimited_storage.hpp
// Copyright 2015-2017 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_UNLIMTED_STORAGE_HPP
#define BOOST_HISTOGRAM_UNLIMTED_STORAGE_HPP
#include <algorithm>
#include <boost/assert.hpp>
#include <boost/config/workaround.hpp>
#include <boost/cstdint.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/iterator/iterator_adaptor.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp>
#include <cmath>
#include <functional>
#include <limits>
#include <memory>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
// version of std::equal_to<> which handles comparison of signed and unsigned
struct equal {
template <class T, class U>
bool operator()(const T& t, const U& u) const noexcept {
return impl(std::is_signed<T>{}, std::is_signed<U>{}, t, u);
}
template <class T, class U>
bool impl(std::false_type, std::false_type, const T& t, const U& u) const noexcept {
return t == u;
}
template <class T, class U>
bool impl(std::false_type, std::true_type, const T& t, const U& u) const noexcept {
return u >= 0 && t == make_unsigned(u);
}
template <class T, class U>
bool impl(std::true_type, std::false_type, const T& t, const U& u) const noexcept {
return t >= 0 && make_unsigned(t) == u;
}
template <class T, class U>
bool impl(std::true_type, std::true_type, const T& t, const U& u) const noexcept {
return t == u;
}
};
// version of std::less<> which handles comparison of signed and unsigned
struct less {
template <class T, class U>
bool operator()(const T& t, const U& u) const noexcept {
return impl(std::is_signed<T>{}, std::is_signed<U>{}, t, u);
}
template <class T, class U>
bool impl(std::false_type, std::false_type, const T& t, const U& u) const noexcept {
return t < u;
}
template <class T, class U>
bool impl(std::false_type, std::true_type, const T& t, const U& u) const noexcept {
return u >= 0 && t < make_unsigned(u);
}
template <class T, class U>
bool impl(std::true_type, std::false_type, const T& t, const U& u) const noexcept {
return t < 0 || make_unsigned(t) < u;
}
template <class T, class U>
bool impl(std::true_type, std::true_type, const T& t, const U& u) const noexcept {
return t < u;
}
};
// version of std::greater<> which handles comparison of signed and unsigned
struct greater {
template <class T, class U>
bool operator()(const T& t, const U& u) const noexcept {
return impl(std::is_signed<T>{}, std::is_signed<U>{}, t, u);
}
template <class T, class U>
bool impl(std::false_type, std::false_type, const T& t, const U& u) const noexcept {
return t > u;
}
template <class T, class U>
bool impl(std::false_type, std::true_type, const T& t, const U& u) const noexcept {
return u < 0 || t > make_unsigned(u);
}
template <class T, class U>
bool impl(std::true_type, std::false_type, const T& t, const U& u) const noexcept {
return t >= 0 && make_unsigned(t) > u;
}
template <class T, class U>
bool impl(std::true_type, std::true_type, const T& t, const U& u) const noexcept {
return t > u;
}
};
template <class Allocator>
struct mp_int;
template <class T>
struct is_unsigned_integral : mp11::mp_and<std::is_integral<T>, std::is_unsigned<T>> {};
template <class T>
bool safe_increment(T& t) {
if (t < std::numeric_limits<T>::max()) {
++t;
return true;
}
return false;
}
template <class T, class U>
bool safe_radd(T& t, const U& u) {
static_assert(is_unsigned_integral<T>::value, "T must be unsigned integral type");
static_assert(is_unsigned_integral<U>::value, "T must be unsigned integral type");
if (static_cast<T>(std::numeric_limits<T>::max() - t) >= u) {
t += static_cast<T>(u); // static_cast to suppress conversion warning
return true;
}
return false;
}
// use boost.multiprecision.cpp_int in your own code, it is much more sophisticated
// than this implementation; we use it here to reduce coupling between boost libs
template <class Allocator>
struct mp_int {
explicit mp_int(Allocator a = {}) : data(1, 0, std::move(a)) {}
explicit mp_int(uint64_t v, Allocator a = {}) : data(1, v, std::move(a)) {}
mp_int(const mp_int&) = default;
mp_int& operator=(const mp_int&) = default;
mp_int(mp_int&&) = default;
mp_int& operator=(mp_int&&) = default;
mp_int& operator=(uint64_t o) {
data = decltype(data)(1, o);
return *this;
}
mp_int& operator++() {
BOOST_ASSERT(data.size() > 0u);
std::size_t i = 0;
while (!safe_increment(data[i])) {
data[i] = 0;
++i;
if (i == data.size()) {
data.push_back(1);
break;
}
}
return *this;
}
mp_int& operator+=(const mp_int& o) {
if (this == &o) {
auto tmp{o};
return operator+=(tmp);
}
bool carry = false;
std::size_t i = 0;
for (uint64_t oi : o.data) {
auto& di = maybe_extend(i);
if (carry) {
if (safe_increment(oi))
carry = false;
else {
++i;
continue;
}
}
if (!safe_radd(di, oi)) {
add_remainder(di, oi);
carry = true;
}
++i;
}
while (carry) {
auto& di = maybe_extend(i);
if (safe_increment(di)) break;
di = 0;
++i;
}
return *this;
}
mp_int& operator+=(uint64_t o) {
BOOST_ASSERT(data.size() > 0u);
if (safe_radd(data[0], o)) return *this;
add_remainder(data[0], o);
// carry the one, data may grow several times
std::size_t i = 1;
while (true) {
auto& di = maybe_extend(i);
if (safe_increment(di)) break;
di = 0;
++i;
}
return *this;
}
operator double() const noexcept {
BOOST_ASSERT(data.size() > 0u);
double result = static_cast<double>(data[0]);
std::size_t i = 0;
while (++i < data.size())
result += static_cast<double>(data[i]) * std::pow(2.0, i * 64);
return result;
}
// total ordering for mp_int, mp_int
bool operator<(const mp_int& o) const noexcept {
BOOST_ASSERT(data.size() > 0u);
BOOST_ASSERT(o.data.size() > 0u);
// no leading zeros allowed
BOOST_ASSERT(data.size() == 1 || data.back() > 0u);
BOOST_ASSERT(o.data.size() == 1 || o.data.back() > 0u);
if (data.size() < o.data.size()) return true;
if (data.size() > o.data.size()) return false;
auto s = data.size();
while (s > 0u) {
--s;
if (data[s] < o.data[s]) return true;
if (data[s] > o.data[s]) return false;
}
return false; // args are equal
}
bool operator==(const mp_int& o) const noexcept {
BOOST_ASSERT(data.size() > 0u);
BOOST_ASSERT(o.data.size() > 0u);
// no leading zeros allowed
BOOST_ASSERT(data.size() == 1 || data.back() > 0u);
BOOST_ASSERT(o.data.size() == 1 || o.data.back() > 0u);
if (data.size() != o.data.size()) return false;
return std::equal(data.begin(), data.end(), o.data.begin());
}
// copied from boost/operators.hpp
friend bool operator>(const mp_int& x, const mp_int& y) { return y < x; }
friend bool operator<=(const mp_int& x, const mp_int& y) { return !(y < x); }
friend bool operator>=(const mp_int& x, const mp_int& y) { return !(x < y); }
friend bool operator!=(const mp_int& x, const mp_int& y) { return !(x == y); }
// total ordering for mp_int, uint64; partial ordering for mp_int, double
template <class U>
bool operator<(const U& o) const noexcept {
BOOST_ASSERT(data.size() > 0u);
return static_if<is_unsigned_integral<U>>(
[this](uint64_t o) { return data.size() == 1 && data[0] < o; },
[this](double o) { return operator double() < o; }, o);
}
template <class U>
bool operator>(const U& o) const noexcept {
BOOST_ASSERT(data.size() > 0u);
BOOST_ASSERT(data.back() > 0u); // no leading zeros allowed
return static_if<is_unsigned_integral<U>>(
[this](uint64_t o) { return data.size() > 1 || data[0] > o; },
[this](double o) { return operator double() > o; }, o);
}
template <class U>
bool operator==(const U& o) const noexcept {
BOOST_ASSERT(data.size() > 0u);
return static_if<is_unsigned_integral<U>>(
[this](uint64_t o) { return data.size() == 1 && data[0] == o; },
[this](double o) { return operator double() == o; }, o);
}
// adapted copy from boost/operators.hpp
template <class U>
friend bool operator<=(const mp_int& x, const U& y) {
if (is_unsigned_integral<U>::value) return !(x > y);
return (x < y) || (x == y);
}
template <class U>
friend bool operator>=(const mp_int& x, const U& y) {
if (is_unsigned_integral<U>::value) return !(x < y);
return (x > y) || (x == y);
}
template <class U>
friend bool operator>(const U& x, const mp_int& y) {
if (is_unsigned_integral<U>::value) return y < x;
return y < x;
}
template <class U>
friend bool operator<(const U& x, const mp_int& y) {
if (is_unsigned_integral<U>::value) return y > x;
return y > x;
}
template <class U>
friend bool operator<=(const U& x, const mp_int& y) {
if (is_unsigned_integral<U>::value) return !(y < x);
return (y > x) || (y == x);
}
template <class U>
friend bool operator>=(const U& x, const mp_int& y) {
if (is_unsigned_integral<U>::value) return !(y > x);
return (y < x) || (y == x);
}
template <class U>
friend bool operator==(const U& y, const mp_int& x) {
return x == y;
}
template <class U>
friend bool operator!=(const U& y, const mp_int& x) {
return !(x == y);
}
template <class U>
friend bool operator!=(const mp_int& y, const U& x) {
return !(y == x);
}
uint64_t& maybe_extend(std::size_t i) {
while (i >= data.size()) data.push_back(0);
return data[i];
}
static void add_remainder(uint64_t& d, const uint64_t o) noexcept {
BOOST_ASSERT(d > 0u);
// in decimal system it would look like this:
// 8 + 8 = 6 = 8 - (9 - 8) - 1
// 9 + 1 = 0 = 9 - (9 - 1) - 1
auto tmp = std::numeric_limits<uint64_t>::max();
tmp -= o;
--d -= tmp;
}
std::vector<uint64_t, Allocator> data;
};
template <class Allocator>
auto create_buffer(Allocator& a, std::size_t n) {
using AT = std::allocator_traits<Allocator>;
auto ptr = AT::allocate(a, n); // may throw
static_assert(std::is_trivially_copyable<decltype(ptr)>::value,
"ptr must be trivially copyable");
auto it = ptr;
const auto end = ptr + n;
try {
// this loop may throw
while (it != end) AT::construct(a, it++, typename AT::value_type{});
} catch (...) {
// release resources that were already acquired before rethrowing
while (it != ptr) AT::destroy(a, --it);
AT::deallocate(a, ptr, n);
throw;
}
return ptr;
}
template <class Allocator, class Iterator>
auto create_buffer(Allocator& a, std::size_t n, Iterator iter) {
BOOST_ASSERT(n > 0u);
using AT = std::allocator_traits<Allocator>;
auto ptr = AT::allocate(a, n); // may throw
static_assert(std::is_trivially_copyable<decltype(ptr)>::value,
"ptr must be trivially copyable");
auto it = ptr;
const auto end = ptr + n;
try {
// this loop may throw
while (it != end) AT::construct(a, it++, *iter++);
} catch (...) {
// release resources that were already acquired before rethrowing
while (it != ptr) AT::destroy(a, --it);
AT::deallocate(a, ptr, n);
throw;
}
return ptr;
}
template <class Allocator>
void destroy_buffer(Allocator& a, typename std::allocator_traits<Allocator>::pointer p,
std::size_t n) {
BOOST_ASSERT(p);
BOOST_ASSERT(n > 0u);
using AT = std::allocator_traits<Allocator>;
auto it = p + n;
while (it != p) AT::destroy(a, --it);
AT::deallocate(a, p, n);
}
} // namespace detail
/**
Memory-efficient storage for integral counters which cannot overflow.
This storage provides a no-overflow-guarantee if it is filled with integral weights
only. This storage implementation keeps a contiguous array of elemental counters, one
for each cell. If an operation is requested, which would overflow a counter, the whole
array is replaced with another of a wider integral type, then the operation is executed.
The storage uses integers of 8, 16, 32, 64 bits, and then switches to a multiprecision
integral type, similar to those in
[Boost.Multiprecision](https://www.boost.org/doc/libs/develop/libs/multiprecision/doc/html/index.html).
A scaling operation or adding a floating point number turns the elements into doubles,
which voids the no-overflow-guarantee.
*/
template <class Allocator>
class unlimited_storage {
static_assert(
std::is_same<typename std::allocator_traits<Allocator>::pointer,
typename std::allocator_traits<Allocator>::value_type*>::value,
"unlimited_storage requires allocator with trivial pointer type");
public:
using allocator_type = Allocator;
using value_type = double;
using mp_int = detail::mp_int<
typename std::allocator_traits<allocator_type>::template rebind_alloc<uint64_t>>;
private:
using types = mp11::mp_list<uint8_t, uint16_t, uint32_t, uint64_t, mp_int, double>;
template <class T>
static constexpr char type_index() noexcept {
return static_cast<char>(mp11::mp_find<types, T>::value);
}
struct buffer_type {
allocator_type alloc;
std::size_t size = 0;
char type = 0;
void* ptr = nullptr;
template <class F, class... Ts>
decltype(auto) apply(F&& f, Ts&&... ts) const {
// this is intentionally not a switch, the if-chain is faster in benchmarks
if (type == type_index<uint8_t>())
return f(static_cast<uint8_t*>(ptr), std::forward<Ts>(ts)...);
if (type == type_index<uint16_t>())
return f(static_cast<uint16_t*>(ptr), std::forward<Ts>(ts)...);
if (type == type_index<uint32_t>())
return f(static_cast<uint32_t*>(ptr), std::forward<Ts>(ts)...);
if (type == type_index<uint64_t>())
return f(static_cast<uint64_t*>(ptr), std::forward<Ts>(ts)...);
if (type == type_index<mp_int>())
return f(static_cast<mp_int*>(ptr), std::forward<Ts>(ts)...);
return f(static_cast<double*>(ptr), std::forward<Ts>(ts)...);
}
buffer_type(allocator_type a = {}) : alloc(std::move(a)) {}
buffer_type(buffer_type&& o) noexcept
: alloc(std::move(o.alloc)), size(o.size), type(o.type), ptr(o.ptr) {
o.size = 0;
o.type = 0;
o.ptr = nullptr;
}
buffer_type& operator=(buffer_type&& o) noexcept {
if (this != &o) {
using std::swap;
swap(alloc, o.alloc);
swap(size, o.size);
swap(type, o.type);
swap(ptr, o.ptr);
}
return *this;
}
buffer_type(const buffer_type& o) : alloc(o.alloc) {
o.apply([this, &o](auto* otp) {
using T = detail::remove_cvref_t<decltype(*otp)>;
this->template make<T>(o.size, otp);
});
}
buffer_type& operator=(const buffer_type& o) {
*this = buffer_type(o);
return *this;
}
~buffer_type() noexcept { destroy(); }
void destroy() noexcept {
BOOST_ASSERT((ptr == nullptr) == (size == 0));
if (ptr == nullptr) return;
apply([this](auto* tp) {
using T = detail::remove_cvref_t<decltype(*tp)>;
using alloc_type =
typename std::allocator_traits<allocator_type>::template rebind_alloc<T>;
alloc_type a(alloc); // rebind allocator
detail::destroy_buffer(a, tp, size);
});
size = 0;
type = 0;
ptr = nullptr;
}
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4244) // possible loss of data
#endif
template <class T>
void make(std::size_t n) {
// note: order of commands is to not leave buffer in invalid state upon throw
destroy();
if (n > 0) {
// rebind allocator
using alloc_type =
typename std::allocator_traits<allocator_type>::template rebind_alloc<T>;
alloc_type a(alloc);
ptr = detail::create_buffer(a, n); // may throw
}
size = n;
type = type_index<T>();
}
template <class T, class U>
void make(std::size_t n, U iter) {
// note: iter may be current ptr, so create new buffer before deleting old buffer
T* new_ptr = nullptr;
const auto new_type = type_index<T>();
if (n > 0) {
// rebind allocator
using alloc_type =
typename std::allocator_traits<allocator_type>::template rebind_alloc<T>;
alloc_type a(alloc);
new_ptr = detail::create_buffer(a, n, iter); // may throw
}
destroy();
size = n;
type = new_type;
ptr = new_ptr;
}
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
};
template <class Buffer>
class reference_t {
public:
reference_t(Buffer* b, std::size_t i) : buffer_(b), idx_(i) {}
reference_t(const reference_t&) = default;
reference_t& operator=(const reference_t&) = delete; // references do not rebind
reference_t& operator=(reference_t&&) = delete; // references do not rebind
// minimal operators for partial ordering
bool operator<(reference_t rhs) const { return op<detail::less>(rhs); }
bool operator>(reference_t rhs) const { return op<detail::greater>(rhs); }
bool operator==(reference_t rhs) const { return op<detail::equal>(rhs); }
// adapted copy from boost/operators.hpp for partial ordering
friend bool operator<=(reference_t x, reference_t y) { return !(y < x); }
friend bool operator>=(reference_t x, reference_t y) { return !(y > x); }
friend bool operator!=(reference_t y, reference_t x) { return !(x == y); }
template <class U>
bool operator<(const U& rhs) const {
return op<detail::less>(rhs);
}
template <class U>
bool operator>(const U& rhs) const {
return op<detail::greater>(rhs);
}
template <class U>
bool operator==(const U& rhs) const {
return op<detail::equal>(rhs);
}
// adapted copy from boost/operators.hpp
template <class U>
friend bool operator<=(reference_t x, const U& y) {
if (detail::is_unsigned_integral<U>::value) return !(x > y);
return (x < y) || (x == y);
}
template <class U>
friend bool operator>=(reference_t x, const U& y) {
if (detail::is_unsigned_integral<U>::value) return !(x < y);
return (x > y) || (x == y);
}
template <class U>
friend bool operator>(const U& x, reference_t y) {
return y < x;
}
template <class U>
friend bool operator<(const U& x, reference_t y) {
return y > x;
}
template <class U>
friend bool operator<=(const U& x, reference_t y) {
if (detail::is_unsigned_integral<U>::value) return !(y < x);
return (y > x) || (y == x);
}
template <class U>
friend bool operator>=(const U& x, reference_t y) {
if (detail::is_unsigned_integral<U>::value) return !(y > x);
return (y < x) || (y == x);
}
template <class U>
friend bool operator==(const U& y, reference_t x) {
return x == y;
}
template <class U>
friend bool operator!=(const U& y, reference_t x) {
return !(x == y);
}
template <class U>
friend bool operator!=(reference_t y, const U& x) {
return !(y == x);
}
operator double() const {
return buffer_->apply(
[this](const auto* tp) { return static_cast<double>(tp[idx_]); });
}
protected:
template <class Binary, class U>
bool op(const reference_t<U>& rhs) const {
const auto i = idx_;
const auto j = rhs.idx_;
return buffer_->apply([i, j, &rhs](const auto* ptr) {
const auto& pi = ptr[i];
return rhs.buffer_->apply([&pi, j](const auto* q) { return Binary()(pi, q[j]); });
});
}
template <class Binary, class U>
bool op(const U& rhs) const {
const auto i = idx_;
return buffer_->apply([i, &rhs](const auto* tp) { return Binary()(tp[i], rhs); });
}
template <class U>
friend class reference_t;
Buffer* buffer_;
std::size_t idx_;
};
public:
using const_reference = reference_t<const buffer_type>;
class reference : public reference_t<buffer_type> {
using base_type = reference_t<buffer_type>;
public:
using base_type::base_type;
reference operator=(reference t) {
t.buffer_->apply([this, &t](const auto* otp) { *this = otp[t.idx_]; });
return *this;
}
reference operator=(const_reference t) {
t.buffer_->apply([this, &t](const auto* otp) { *this = otp[t.idx_]; });
return *this;
}
template <class U>
reference operator=(const U& t) {
base_type::buffer_->apply([this, &t](auto* tp) {
tp[this->idx_] = 0;
adder()(tp, *(this->buffer_), this->idx_, t);
});
return *this;
}
template <class U>
reference operator+=(const U& t) {
base_type::buffer_->apply(adder(), *base_type::buffer_, base_type::idx_, t);
return *this;
}
template <class U>
reference operator*=(const U& t) {
base_type::buffer_->apply(multiplier(), *base_type::buffer_, base_type::idx_, t);
return *this;
}
template <class U>
reference operator-=(const U& t) {
return operator+=(-t);
}
template <class U>
reference operator/=(const U& t) {
return operator*=(1.0 / static_cast<double>(t));
}
reference operator++() {
base_type::buffer_->apply(incrementor(), *base_type::buffer_, base_type::idx_);
return *this;
}
// minimal operators for partial ordering
bool operator<(reference rhs) const { return base_type::operator<(rhs); }
bool operator>(reference rhs) const { return base_type::operator>(rhs); }
bool operator==(reference rhs) const { return base_type::operator==(rhs); }
// adapted copy from boost/operators.hpp for partial ordering
friend bool operator<=(reference x, reference y) { return !(y < x); }
friend bool operator>=(reference x, reference y) { return !(y > x); }
friend bool operator!=(reference y, reference x) { return !(x == y); }
};
private:
template <class Value, class Reference, class Buffer>
class iterator_t
: public boost::iterator_adaptor<iterator_t<Value, Reference, Buffer>, std::size_t,
Value, std::random_access_iterator_tag, Reference,
std::ptrdiff_t> {
public:
iterator_t() = default;
template <class V, class R, class B>
iterator_t(const iterator_t<V, R, B>& it)
: iterator_t::iterator_adaptor_(it.base()), buffer_(it.buffer_) {}
iterator_t(Buffer* b, std::size_t i) noexcept
: iterator_t::iterator_adaptor_(i), buffer_(b) {}
protected:
template <class V, class R, class B>
bool equal(const iterator_t<V, R, B>& rhs) const noexcept {
return buffer_ == rhs.buffer_ && this->base() == rhs.base();
}
Reference dereference() const { return {buffer_, this->base()}; }
friend class ::boost::iterator_core_access;
template <class V, class R, class B>
friend class iterator_t;
private:
Buffer* buffer_ = nullptr;
};
public:
using const_iterator = iterator_t<const value_type, const_reference, const buffer_type>;
using iterator = iterator_t<value_type, reference, buffer_type>;
explicit unlimited_storage(allocator_type a = {}) : buffer(std::move(a)) {}
unlimited_storage(const unlimited_storage&) = default;
unlimited_storage& operator=(const unlimited_storage&) = default;
unlimited_storage(unlimited_storage&&) = default;
unlimited_storage& operator=(unlimited_storage&&) = default;
template <class T>
unlimited_storage(const storage_adaptor<T>& s) {
using V = detail::remove_cvref_t<decltype(s[0])>;
constexpr auto ti = type_index<V>();
detail::static_if_c<(ti < mp11::mp_size<types>::value)>(
[&](auto) { buffer.template make<V>(s.size(), s.begin()); },
[&](auto) { buffer.template make<double>(s.size(), s.begin()); }, 0);
}
template <class Iterable, class = detail::requires_iterable<Iterable>>
unlimited_storage& operator=(const Iterable& s) {
*this = unlimited_storage(s);
return *this;
}
allocator_type get_allocator() const { return buffer.alloc; }
void reset(std::size_t s) { buffer.template make<uint8_t>(s); }
std::size_t size() const noexcept { return buffer.size; }
reference operator[](std::size_t i) noexcept { return {&buffer, i}; }
const_reference operator[](std::size_t i) const noexcept { return {&buffer, i}; }
bool operator==(const unlimited_storage& o) const noexcept {
if (size() != o.size()) return false;
return buffer.apply([&o](const auto* ptr) {
return o.buffer.apply([ptr, &o](const auto* optr) {
return std::equal(ptr, ptr + o.size(), optr, detail::equal{});
});
});
}
template <class T>
bool operator==(const T& o) const {
if (size() != o.size()) return false;
return buffer.apply([&o](const auto* ptr) {
return std::equal(ptr, ptr + o.size(), std::begin(o), detail::equal{});
});
}
unlimited_storage& operator*=(const double x) {
buffer.apply(multiplier(), buffer, x);
return *this;
}
iterator begin() noexcept { return {&buffer, 0}; }
iterator end() noexcept { return {&buffer, size()}; }
const_iterator begin() const noexcept { return {&buffer, 0}; }
const_iterator end() const noexcept { return {&buffer, size()}; }
/// @private used by unit tests, not part of generic storage interface
template <class T>
unlimited_storage(std::size_t s, const T* p, allocator_type a = {})
: buffer(std::move(a)) {
buffer.template make<T>(s, p);
}
template <class Archive>
void serialize(Archive&, unsigned);
private:
struct incrementor {
template <class T, class Buffer>
void operator()(T* tp, Buffer& b, std::size_t i) {
if (!detail::safe_increment(tp[i])) {
using U = mp11::mp_at_c<types, (type_index<T>() + 1)>;
b.template make<U>(b.size, tp);
++static_cast<U*>(b.ptr)[i];
}
}
template <class Buffer>
void operator()(mp_int* tp, Buffer&, std::size_t i) {
++tp[i];
}
template <class Buffer>
void operator()(double* tp, Buffer&, std::size_t i) {
++tp[i];
}
};
struct adder {
template <class Buffer, class U>
void operator()(double* tp, Buffer&, std::size_t i, const U& x) {
tp[i] += static_cast<double>(x);
}
template <class T, class Buffer, class U>
void operator()(T* tp, Buffer& b, std::size_t i, const U& x) {
U_is_integral(std::is_integral<U>{}, tp, b, i, x);
}
template <class T, class Buffer, class U>
void U_is_integral(std::false_type, T* tp, Buffer& b, std::size_t i, const U& x) {
b.template make<double>(b.size, tp);
operator()(static_cast<double*>(b.ptr), b, i, x);
}
template <class T, class Buffer, class U>
void U_is_integral(std::true_type, T* tp, Buffer& b, std::size_t i, const U& x) {
U_is_unsigned_integral(std::is_unsigned<U>{}, tp, b, i, x);
}
template <class T, class Buffer, class U>
void U_is_unsigned_integral(std::false_type, T* tp, Buffer& b, std::size_t i,
const U& x) {
if (x >= 0)
U_is_unsigned_integral(std::true_type{}, tp, b, i, detail::make_unsigned(x));
else
U_is_integral(std::false_type{}, tp, b, i, static_cast<double>(x));
}
template <class Buffer, class U>
void U_is_unsigned_integral(std::true_type, mp_int* tp, Buffer&, std::size_t i,
const U& x) {
tp[i] += x;
}
template <class T, class Buffer, class U>
void U_is_unsigned_integral(std::true_type, T* tp, Buffer& b, std::size_t i,
const U& x) {
if (detail::safe_radd(tp[i], x)) return;
using V = mp11::mp_at_c<types, (type_index<T>() + 1)>;
b.template make<V>(b.size, tp);
U_is_unsigned_integral(std::true_type{}, static_cast<V*>(b.ptr), b, i, x);
}
};
struct multiplier {
template <class T, class Buffer>
void operator()(T* tp, Buffer& b, const double x) {
// potential lossy conversion that cannot be avoided
b.template make<double>(b.size, tp);
operator()(static_cast<double*>(b.ptr), b, x);
}
template <class Buffer>
void operator()(double* tp, Buffer& b, const double x) {
for (auto end = tp + b.size; tp != end; ++tp) *tp *= x;
}
template <class T, class Buffer, class U>
void operator()(T* tp, Buffer& b, std::size_t i, const U& x) {
b.template make<double>(b.size, tp);
operator()(static_cast<double*>(b.ptr), b, i, x);
}
template <class Buffer, class U>
void operator()(double* tp, Buffer&, std::size_t i, const U& x) {
tp[i] *= static_cast<double>(x);
}
};
buffer_type buffer;
};
} // namespace histogram
} // namespace boost
#endif