10 changes: 8 additions & 2 deletions include/boost/histogram/detail/fill_n.hpp
Expand Up @@ -20,7 +20,9 @@
#include <boost/histogram/detail/span.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/bind.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <boost/variant2/variant.hpp>
#include <stdexcept>
Expand Down Expand Up @@ -278,7 +280,7 @@ void fill_n_check_extra_args(std::size_t n, weight_type<T>&& w, Ts&&... ts) {
inline void fill_n_check_extra_args(std::size_t) noexcept {}

template <class S, class A, class T, std::size_t N, class... Us>
void fill_n(const std::size_t offset, S& storage, A& axes,
void fill_n(std::true_type, const std::size_t offset, S& storage, A& axes,
const dtl::span<const T, N> values, Us&&... us) {
static_assert(!std::is_pointer<T>::value,
"passing iterable of pointers not allowed (cannot determine lengths); "
Expand All @@ -303,6 +305,10 @@ void fill_n(const std::size_t offset, S& storage, A& axes,
values, std::forward<Us>(us)...);
}

// empty implementation for bad arguments to stop compiler from showing internals
template <class... Ts>
void fill_n(std::false_type, Ts...) {}

} // namespace detail
} // namespace histogram
} // namespace boost
Expand Down
92 changes: 70 additions & 22 deletions include/boost/histogram/histogram.hpp
Expand Up @@ -7,19 +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/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>
Expand Down Expand Up @@ -51,12 +56,9 @@ namespace histogram {
*/
template <class Axes, class Storage>
class histogram : detail::mutex_base<Axes, Storage> {
using mutex_base_t = typename detail::mutex_base<Axes, Storage>;

public:
static_assert(mp11::mp_size<Axes>::value > 0, "at least one axis required");
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;
Expand All @@ -66,6 +68,10 @@ class histogram : detail::mutex_base<Axes, Storage> {
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;

template <class A, class S>
Expand Down Expand Up @@ -174,16 +180,28 @@ class histogram : detail::mutex_base<Axes, Storage> {
`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... Args>
iterator operator()(const Args&... args) {
return operator()(std::forward_as_tuple(args...));
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...>& args) {
std::lock_guard<typename mutex_base_t::type> guard{mutex_base_t::get()};
return detail::fill(offset_, storage_, axes_, 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.
Expand All @@ -203,8 +221,14 @@ class histogram : detail::mutex_base<Axes, Storage> {
*/
template <class Iterable, class = detail::requires_iterable<Iterable>>
void fill(const Iterable& args) {
std::lock_guard<typename mutex_base_t::type> guard{mutex_base_t::get()};
detail::fill_n(offset_, storage_, axes_, detail::make_span(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.
Expand All @@ -214,8 +238,15 @@ class histogram : detail::mutex_base<Axes, Storage> {
*/
template <class Iterable, class T, class = detail::requires_iterable<Iterable>>
void fill(const Iterable& args, const weight_type<T>& weights) {
std::lock_guard<typename mutex_base_t::type> guard{mutex_base_t::get()};
detail::fill_n(offset_, storage_, axes_, detail::make_span(args),
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)));
}

Expand All @@ -234,13 +265,20 @@ class histogram : detail::mutex_base<Axes, Storage> {
@param args iterable of values.
@param samples single sample or an iterable of samples.
*/
template <class Iterable, class T, class = detail::requires_iterable<Iterable>>
void fill(const Iterable& args, const sample_type<T>& samples) {
std::lock_guard<typename mutex_base_t::type> guard{mutex_base_t::get()};
template <class Iterable, class... Ts, class = detail::requires_iterable<Iterable>,
class = mp11::mp_list<detail::requires_iterable<Ts>...>>
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::data(std::declval<Ts>()))...>;
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) {
detail::fill_n(offset_, storage_, axes_, detail::make_span(args),
detail::to_ptr_size(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);
}
Expand All @@ -255,13 +293,23 @@ class histogram : detail::mutex_base<Axes, Storage> {
fill(args, samples);
}

template <class Iterable, class T, class U, class = detail::requires_iterable<Iterable>>
template <class Iterable, class T, class... Ts,
class = detail::requires_iterable<Iterable>,
class = mp11::mp_list<detail::requires_iterable<Ts>...>>
void fill(const Iterable& args, const weight_type<T>& weights,
const sample_type<U>& samples) {
std::lock_guard<typename mutex_base_t::type> guard{mutex_base_t::get()};
const sample_type<std::tuple<Ts...>>& samples) {
using acc_traits = detail::accumulator_traits<value_type>;
using sample_args = std::tuple<decltype(*detail::data(std::declval<Ts>()))...>;
detail::sample_args_passed_vs_expected<sample_args, typename acc_traits::args>();
std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
mp11::tuple_apply(
[&](const auto&... sargs) {
detail::fill_n(offset_, storage_, axes_, detail::make_span(args),
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, 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)...);
},
Expand Down
15 changes: 11 additions & 4 deletions test/CMakeLists.txt
Expand Up @@ -54,16 +54,19 @@ boost_test(TYPE compile-fail SOURCES make_histogram_fail0.cpp
boost_test(TYPE compile-fail SOURCES make_histogram_fail1.cpp
LIBRARIES Boost::histogram
)
boost_test(TYPE compile-fail SOURCES profile_fail0.cpp
boost_test(TYPE compile-fail SOURCES histogram_fail0.cpp
LIBRARIES Boost::histogram
)
boost_test(TYPE compile-fail SOURCES profile_fail1.cpp
boost_test(TYPE compile-fail SOURCES histogram_fail1.cpp
LIBRARIES Boost::histogram
)
boost_test(TYPE compile-fail SOURCES profile_fail2.cpp
boost_test(TYPE compile-fail SOURCES histogram_fail2.cpp
LIBRARIES Boost::histogram
)
boost_test(TYPE compile-fail SOURCES profile_fail3.cpp
boost_test(TYPE compile-fail SOURCES histogram_fail3.cpp
LIBRARIES Boost::histogram
)
boost_test(TYPE compile-fail SOURCES histogram_fail4.cpp
LIBRARIES Boost::histogram
)
boost_test(TYPE run SOURCES accumulators_test.cpp
Expand Down Expand Up @@ -92,6 +95,10 @@ boost_test(TYPE run SOURCES axis_variable_test.cpp
LIBRARIES Boost::histogram Boost::core)
boost_test(TYPE run SOURCES axis_variant_test.cpp
LIBRARIES Boost::histogram Boost::core)
boost_test(TYPE run SOURCES detail_accumulator_traits_test.cpp
LIBRARIES Boost::histogram Boost::core)
boost_test(TYPE run SOURCES detail_argument_traits_test.cpp
LIBRARIES Boost::histogram Boost::core)
boost_test(TYPE run SOURCES detail_args_type_test.cpp
LIBRARIES Boost::histogram Boost::core)
boost_test(TYPE run SOURCES detail_axes_test.cpp
Expand Down
11 changes: 7 additions & 4 deletions test/Jamfile
Expand Up @@ -51,6 +51,8 @@ alias cxx14 :
[ run axis_traits_test.cpp ]
[ run axis_variable_test.cpp ]
[ run axis_variant_test.cpp ]
[ run detail_accumulator_traits_test.cpp ]
[ run detail_argument_traits_test.cpp ]
[ run detail_args_type_test.cpp ]
[ run detail_axes_test.cpp ]
[ run detail_convert_integer_test.cpp ]
Expand Down Expand Up @@ -101,10 +103,11 @@ alias failure :
[ compile-fail axis_variable_fail1.cpp ]
[ compile-fail make_histogram_fail0.cpp ]
[ compile-fail make_histogram_fail1.cpp ]
[ compile-fail profile_fail0.cpp ]
[ compile-fail profile_fail1.cpp ]
[ compile-fail profile_fail2.cpp ]
[ compile-fail profile_fail3.cpp ]
[ compile-fail histogram_fail0.cpp ]
[ compile-fail histogram_fail1.cpp ]
[ compile-fail histogram_fail2.cpp ]
[ compile-fail histogram_fail3.cpp ]
[ compile-fail histogram_fail4.cpp ]
;

alias threading :
Expand Down
1 change: 0 additions & 1 deletion test/axis_traits_test.cpp
Expand Up @@ -8,7 +8,6 @@
#include <boost/core/lightweight_test_trait.hpp>
#include <boost/histogram/axis.hpp>
#include <boost/histogram/axis/traits.hpp>
#include <boost/mp11.hpp>
#include "std_ostream.hpp"
#include "throw_exception.hpp"
#include "utility_axis.hpp"
Expand Down
82 changes: 82 additions & 0 deletions test/detail_accumulator_traits_test.cpp
@@ -0,0 +1,82 @@
// Copyright 2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/core/lightweight_test.hpp>
#include <boost/core/lightweight_test_trait.hpp>
#include <boost/histogram/detail/accumulator_traits.hpp>
#include <boost/histogram/weight.hpp>
#include <tuple>

namespace dtl = boost::histogram::detail;

int main() {
using boost::histogram::weight_type;

struct A1 {
void operator()(){};
};

BOOST_TEST_NOT(dtl::accumulator_traits<A1>::wsupport::value);
BOOST_TEST_TRAIT_SAME(typename dtl::accumulator_traits<A1>::args, std::tuple<>);

struct A2 {
void operator()(int, double) {}
};

BOOST_TEST_NOT(dtl::accumulator_traits<A2>::wsupport::value);
BOOST_TEST_TRAIT_SAME(typename dtl::accumulator_traits<A2>::args,
std::tuple<int, double>);

struct A3 {
void operator()() {}
void operator()(weight_type<int>) {}
};

BOOST_TEST(dtl::accumulator_traits<A3>::wsupport::value);
BOOST_TEST_TRAIT_SAME(typename dtl::accumulator_traits<A3>::args, std::tuple<>);

struct A4 {
void operator()(int, double, char) {}
void operator()(weight_type<int>, int, double, char) {}
};

BOOST_TEST(dtl::accumulator_traits<A4>::wsupport::value);
BOOST_TEST_TRAIT_SAME(typename dtl::accumulator_traits<A4>::args,
std::tuple<int, double, char>);

struct A5 {
void operator()(const weight_type<int>, int) {}
};

BOOST_TEST(dtl::accumulator_traits<A5>::wsupport::value);
BOOST_TEST_TRAIT_SAME(typename dtl::accumulator_traits<A5>::args, std::tuple<int>);

struct A6 {
void operator()(const weight_type<int>&, const int) {}
};

BOOST_TEST(dtl::accumulator_traits<A6>::wsupport::value);
BOOST_TEST_TRAIT_SAME(typename dtl::accumulator_traits<A6>::args, std::tuple<int>);

struct A7 {
void operator()(weight_type<int>&&, int&&) {}
};

BOOST_TEST(dtl::accumulator_traits<A7>::wsupport::value);
BOOST_TEST_TRAIT_SAME(typename dtl::accumulator_traits<A7>::args, std::tuple<int&&>);

struct B {
int operator+=(int) { return 0; }
};

BOOST_TEST(dtl::accumulator_traits<B>::wsupport::value);
BOOST_TEST_TRAIT_SAME(typename dtl::accumulator_traits<B>::args, std::tuple<>);

BOOST_TEST(dtl::accumulator_traits<int>::wsupport::value);
BOOST_TEST_TRAIT_SAME(dtl::accumulator_traits<int>::args, std::tuple<>);

return boost::report_errors();
}
61 changes: 61 additions & 0 deletions test/detail_argument_traits_test.cpp
@@ -0,0 +1,61 @@
// Copyright 2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/core/lightweight_test.hpp>
#include <boost/core/lightweight_test_trait.hpp>
#include <boost/histogram/detail/argument_traits.hpp>
#include <boost/histogram/sample.hpp>
#include <boost/histogram/weight.hpp>

namespace dtl = boost::histogram::detail;

int main() {
using boost::histogram::sample;
using boost::histogram::weight;

// is_weight
{
struct A {};
using B = int;
BOOST_TEST_NOT(dtl::is_weight<A>::value);
BOOST_TEST_NOT(dtl::is_weight<B>::value);
BOOST_TEST_NOT(dtl::is_weight<decltype(sample(0))>::value);
BOOST_TEST(dtl::is_weight<decltype(weight(0))>::value);
}

// is_sample
{
struct A {};
using B = int;
BOOST_TEST_NOT(dtl::is_sample<A>::value);
BOOST_TEST_NOT(dtl::is_sample<B>::value);
BOOST_TEST_NOT(dtl::is_sample<decltype(weight(0))>::value);
BOOST_TEST(dtl::is_sample<decltype(sample(0))>::value);
BOOST_TEST(dtl::is_sample<decltype(sample(0, A{}))>::value);
}

using T1 = dtl::argument_traits<int>;
using E1 = dtl::argument_traits_holder<1, 0, -1, -1, std::tuple<>>;
BOOST_TEST_TRAIT_SAME(T1, E1);

using T2 = dtl::argument_traits<int, int>;
using E2 = dtl::argument_traits_holder<2, 0, -1, -1, std::tuple<>>;
BOOST_TEST_TRAIT_SAME(T2, E2);

using T3 = dtl::argument_traits<decltype(weight(0)), int, int>;
using E3 = dtl::argument_traits_holder<2, 1, 0, -1, std::tuple<>>;
BOOST_TEST_TRAIT_SAME(T3, E3);

using T4 = dtl::argument_traits<decltype(weight(0)), int, int, decltype(sample(0))>;
using E4 = dtl::argument_traits_holder<2, 1, 0, 3, std::tuple<int>>;
BOOST_TEST_TRAIT_SAME(T4, E4);

using T5 = dtl::argument_traits<int, decltype(sample(0, 0.0))>;
using E5 = dtl::argument_traits_holder<1, 0, -1, 1, std::tuple<int, double>>;
BOOST_TEST_TRAIT_SAME(T5, E5);

return boost::report_errors();
}
20 changes: 0 additions & 20 deletions test/detail_detect_test.cpp
Expand Up @@ -269,26 +269,6 @@ int main() {
BOOST_TEST_TRAIT_TRUE((is_sequence_of_any_axis<decltype(v)>));
}

// is_weight
{
struct A {};
using B = int;
using C = weight_type<int>;
BOOST_TEST_TRAIT_FALSE((is_weight<A>));
BOOST_TEST_TRAIT_FALSE((is_weight<B>));
BOOST_TEST_TRAIT_TRUE((is_weight<C>));
}

// is_sample
{
struct A {};
using B = int;
using C = sample_type<int>;
BOOST_TEST_TRAIT_FALSE((is_sample<A>));
BOOST_TEST_TRAIT_FALSE((is_sample<B>));
BOOST_TEST_TRAIT_TRUE((is_sample<C>));
}

// has_operator_equal
{
struct A {};
Expand Down
12 changes: 9 additions & 3 deletions test/profile_fail0.cpp → test/histogram_fail0.cpp
Expand Up @@ -5,10 +5,16 @@
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/histogram/axis/integer.hpp>
#include <boost/histogram/make_profile.hpp>
#include <boost/histogram/make_histogram.hpp>

int main() {
using namespace boost::histogram;
auto h = make_profile(axis::integer<>(0, 5));
h(0, weight(1)); // profile requires a sample

auto h = make_histogram(axis::integer<>(0, 5));

// invalid sample argument
h(0, sample(1));

auto values = {0, 1};
h.fill(values, sample(values)); // invalid sample argument
}
16 changes: 13 additions & 3 deletions test/profile_fail2.cpp → test/histogram_fail1.cpp
Expand Up @@ -5,10 +5,20 @@
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/histogram/axis/integer.hpp>
#include <boost/histogram/make_profile.hpp>
#include <boost/histogram/make_histogram.hpp>

int main() {
using namespace boost::histogram;
auto h = make_weighted_profile(axis::integer<>(0, 5));
h(0); // weighted profile requires a sample

struct accumulator {
void operator()() {}
};

auto h = make_histogram_with(dense_storage<accumulator>(), axis::integer<>(0, 5));

// invalid weight argument
h(0, weight(1));

auto values = {0, 1};
h.fill(values, weight(1));
}
15 changes: 13 additions & 2 deletions test/profile_fail1.cpp → test/histogram_fail2.cpp
Expand Up @@ -9,6 +9,17 @@

int main() {
using namespace boost::histogram;
auto h = make_profile(axis::integer<>(0, 5));
h(0, sample(1, 2)); // profile requires one sample

struct accumulator {
void operator()(double) {}
void operator()(weight_type<double>, double) {}
};

auto h = make_histogram_with(dense_storage<accumulator>(), axis::integer<>(0, 5));

// accumulator requires sample
h(0, weight(1));

auto values = {1, 2};
h.fill(values, weight(1));
}
23 changes: 23 additions & 0 deletions test/histogram_fail3.cpp
@@ -0,0 +1,23 @@
// Copyright 2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/histogram/axis/integer.hpp>
#include <boost/histogram/make_histogram.hpp>

int main() {
struct accumulator {
void operator()(double) {}
};

using namespace boost::histogram;
auto h = make_histogram_with(dense_storage<accumulator>(), axis::integer<>(0, 5));

// wrong number of sample arguments
h(0, sample(1, 2));

auto values = {0, 1};
h.fill(values, sample(values, values));
}
24 changes: 24 additions & 0 deletions test/histogram_fail4.cpp
@@ -0,0 +1,24 @@
// Copyright 2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/histogram/axis/integer.hpp>
#include <boost/histogram/make_histogram.hpp>

int main() {
using namespace boost::histogram;

struct accumulator {
void operator()(std::string) {}
};

auto h = make_histogram_with(dense_storage<accumulator>(), axis::integer<>(0, 5));

// invalid weight argument
h(0, sample(1));

auto values = {1, 2};
h.fill(values, sample(values));
}
14 changes: 0 additions & 14 deletions test/profile_fail3.cpp

This file was deleted.