From 170199bb9f856e091eef151ac3742eebf1810ac8 Mon Sep 17 00:00:00 2001 From: Hans Dembinski Date: Tue, 30 Apr 2019 16:27:14 +0200 Subject: [PATCH] replacing boost::variant with detail::variant - removes large number of outdated indirect boost dependencies - less limited in number of stored types --- .appveyor.yml | 5 +- include/boost/histogram/axis/variant.hpp | 188 ++++++++------ include/boost/histogram/detail/axes.hpp | 28 +- include/boost/histogram/detail/linearize.hpp | 1 - include/boost/histogram/detail/meta.hpp | 17 +- include/boost/histogram/detail/try_cast.hpp | 19 +- include/boost/histogram/detail/variant.hpp | 242 ++++++++++++++++++ .../detail/variant_serialization.hpp | 67 +++++ include/boost/histogram/histogram.hpp | 1 + include/boost/histogram/indexed.hpp | 1 - include/boost/histogram/serialization.hpp | 8 +- include/boost/histogram/unsafe_access.hpp | 17 +- test/Jamfile | 12 +- test/axis_variant_test.cpp | 49 +++- test/detail_meta_test.cpp | 21 -- test/detail_misc_test.cpp | 5 +- test/detail_variant_serialization_test.cpp | 33 +++ test/detail_variant_serialization_test.xml | 8 + test/detail_variant_test.cpp | 238 +++++++++++++++++ tools/cov.sh | 2 +- 20 files changed, 811 insertions(+), 151 deletions(-) create mode 100644 include/boost/histogram/detail/variant.hpp create mode 100644 include/boost/histogram/detail/variant_serialization.hpp create mode 100644 test/detail_variant_serialization_test.cpp create mode 100644 test/detail_variant_serialization_test.xml create mode 100644 test/detail_variant_test.cpp diff --git a/.appveyor.yml b/.appveyor.yml index e86c996e..cac1ffd3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -22,14 +22,15 @@ install: # Replacing Boost module with this project and installing Boost dependencies - xcopy /s /e /q %APPVEYOR_BUILD_FOLDER% libs\histogram\ - - python tools\boostdep\depinst\depinst.py --git_args "--depth 10 --jobs 2" histogram + - python tools\boostdep\depinst\depinst.py -N units -N range -N accumulators --git_args "--depth 10 --jobs 2" histogram # Adding missing toolsets and preparing Boost headers - cmd /c bootstrap - b2 headers test_script: - - b2 -j2 -q libs\histogram\test + - cd libs\histogram\test + - ..\..\..\b2 -j2 -q minimal serialization on_failure: ## Uncomment the following line to stop VM and enable interactive login diff --git a/include/boost/histogram/axis/variant.hpp b/include/boost/histogram/axis/variant.hpp index 15908834..f29f50a6 100644 --- a/include/boost/histogram/axis/variant.hpp +++ b/include/boost/histogram/axis/variant.hpp @@ -7,7 +7,6 @@ #ifndef BOOST_HISTOGRAM_AXIS_VARIANT_HPP #define BOOST_HISTOGRAM_AXIS_VARIANT_HPP -#include #include #include #include @@ -15,22 +14,13 @@ #include #include #include +#include #include #include #include #include #include -#if BOOST_WORKAROUND(BOOST_CLANG, >= 1) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-parameter" -#endif -#include -#include -#include -#include -#if BOOST_WORKAROUND(BOOST_CLANG, >= 1) -#pragma clang diagnostic pop -#endif +#include #include #include #include @@ -39,29 +29,75 @@ namespace boost { namespace histogram { +namespace detail { + +template +struct ref_handler_impl { + static constexpr bool is_ref = false; + using type = T; +}; + +template +struct ref_handler_impl> { + static constexpr bool is_ref = true; + using type = std::reference_wrapper; +}; + +template +struct ref_handler_impl> { + static constexpr bool is_ref = true; + using type = std::reference_wrapper; +}; + +template +using ref_handler = ref_handler_impl>>; + +struct variant_access { + template + static decltype(auto) get(Variant&& v) { + using H = ref_handler; + auto&& ref = v.impl.template get(); + return static_if_c([](auto&& ref) -> decltype(auto) { return ref.get(); }, + [](auto&& ref) -> decltype(auto) { return ref; }, ref); + } + + template + static auto get_if(Variant&& v) noexcept { + using H = ref_handler; + auto p = v.impl.template get_if(); + return static_if_c([](auto p) -> auto { return p ? &p->get() : nullptr; }, + [](auto p) -> auto { return p; }, p); + } + + template + static decltype(auto) apply(Visitor&& vis, Variant&& v) { + using H = ref_handler; + return static_if_c( + [](auto&& vis, auto&& v) -> decltype(auto) { + return v.apply([&vis](auto&& ref) -> decltype(auto) { return vis(ref.get()); }); + }, + [](auto&& vis, auto&& v) -> decltype(auto) { return v.apply(vis); }, vis, v.impl); + } +}; + +} // namespace detail + namespace axis { -template -T* get_if(variant* v); - -template -const T* get_if(const variant* v); - /// Polymorphic axis type template class variant : public iterator_mixin> { - using impl_type = boost::variant; - using raw_types = mp11::mp_transform; + using impl_type = detail::variant; template - using is_bounded_type = mp11::mp_contains>; + using is_bounded_type = mp11::mp_contains>; template using requires_bounded_type = std::enable_if_t::value>; // maybe metadata_type or const metadata_type, if bounded type is const using metadata_type = std::remove_reference_t>()))>; + traits::metadata(std::declval>>()))>; public: // cannot import ctors with using directive, it breaks gcc and msvc @@ -71,10 +107,10 @@ public: variant(variant&&) = default; variant& operator=(variant&&) = default; - template > + template > variant(T&& t) : impl(std::forward(t)) {} - template > + template > variant& operator=(T&& t) { impl = std::forward(t); return *this; @@ -105,12 +141,12 @@ public: /// Return size of axis. index_type size() const { - return visit([](const auto& x) { return x.size(); }, *this); + return visit([](const auto& a) { return a.size(); }, *this); } /// Return options of axis or option::none_t if axis has no options. unsigned options() const { - return visit([](const auto& x) { return axis::traits::options(x); }, *this); + return visit([](const auto& a) { return axis::traits::options(a); }, *this); } /// Return reference to const metadata or instance of null_type if axis has no @@ -138,7 +174,7 @@ public: /// metadata. metadata_type& metadata() { return visit( - [](auto&& a) -> metadata_type& { + [](auto& a) -> metadata_type& { using M = decltype(traits::metadata(a)); return detail::static_if>( [](auto& a) -> metadata_type& { return traits::metadata(a); }, @@ -180,15 +216,12 @@ public: [idx](const auto& a) { return detail::value_method_switch( [idx](const auto& a) { // axis is discrete - const double x = - detail::try_cast(a.value(idx)); + const double x = traits::value_as(a, idx); return polymorphic_bin(x, x); }, [idx](const auto& a) { // axis is continuous - const double x1 = - detail::try_cast(a.value(idx)); - const double x2 = - detail::try_cast(a.value(idx + 1)); + const double x1 = traits::value_as(a, idx); + const double x2 = traits::value_as(a, idx + 1); return polymorphic_bin(x1, x2); }, a); @@ -203,8 +236,7 @@ public: template bool operator==(const T& t) const { - // boost::variant::operator==(T) implemented only to fail, cannot use it - auto tp = get_if(this); + const T* tp = detail::variant_access::template get_if(*this); return tp && detail::relaxed_equal(*tp, t); } @@ -213,74 +245,65 @@ public: return !operator==(t); } - template - void serialize(Archive& ar, unsigned); - - template - friend auto visit(Visitor&&, Variant &&) - -> detail::visitor_return_type; - - template - friend T& get(variant& v); - - template - friend const T& get(const variant& v); - - template - friend T&& get(variant&& v); - - template - friend T* get_if(variant* v); - - template - friend const T* get_if(const variant* v); - private: - boost::variant impl; + impl_type impl; + + friend struct detail::variant_access; + friend struct boost::histogram::unsafe_access; }; -/// Apply visitor to variant. -template -auto visit(Visitor&& vis, Variant&& var) - -> detail::visitor_return_type { - return boost::apply_visitor(std::forward(vis), var.impl); +// specialization for empty argument list, useful for meta-programming +template <> +class variant<> {}; + +/// Apply visitor to variant (reference). +template +decltype(auto) visit(Visitor&& vis, variant& var) { + return detail::variant_access::apply(vis, var); } -/// Return lvalue reference to T, throws unspecified exception if type does not -/// match. -template -T& get(variant& v) { - return boost::get(v.impl); +/// Apply visitor to variant (movable reference). +template +decltype(auto) visit(Visitor&& vis, variant&& var) { + return detail::variant_access::apply(vis, std::move(var)); } -/// Return rvalue reference to T, throws unspecified exception if type does not -/// match. -template -T&& get(variant&& v) { - return boost::get(std::move(v.impl)); +/// Apply visitor to variant (const reference). +template +decltype(auto) visit(Visitor&& vis, const variant& var) { + return detail::variant_access::apply(vis, var); } -/// Return const reference to T, throws unspecified exception if type does not -/// match. +/// Return reference to T, throws unspecified exception if type does not match. template -const T& get(const variant& v) { - return boost::get(v.impl); +decltype(auto) get(variant& v) { + return detail::variant_access::template get(v); +} + +/// Return movable reference to T, throws unspecified exception if type does not match. +template +decltype(auto) get(variant&& v) { + return std::move(detail::variant_access::template get(v)); +} + +/// Return const reference to T, throws unspecified exception if type does not match. +template +decltype(auto) get(const variant& v) { + return detail::variant_access::template get(v); } /// Returns pointer to T in variant or null pointer if type does not match. template T* get_if(variant* v) { - return boost::relaxed_get(&(v->impl)); + return detail::variant_access::template get_if(*v); } -/// Returns pointer to const T in variant or null pointer if type does not -/// match. +/// Returns pointer to const T in variant or null pointer if type does not match. template const T* get_if(const variant* v) { - return boost::relaxed_get(&(v->impl)); + return detail::variant_access::template get_if(*v); } -#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED // pass-through version of get for generic programming template decltype(auto) get(U&& u) { @@ -300,7 +323,6 @@ const T* get_if(const U* u) { return std::is_same>::value ? reinterpret_cast(u) : nullptr; } -#endif } // namespace axis } // namespace histogram diff --git a/include/boost/histogram/detail/axes.hpp b/include/boost/histogram/detail/axes.hpp index a7054abd..004f6b49 100644 --- a/include/boost/histogram/detail/axes.hpp +++ b/include/boost/histogram/detail/axes.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -47,18 +48,19 @@ decltype(auto) axis_get(const T& axes) { template decltype(auto) axis_get(std::tuple& axes, unsigned i) { + using namespace boost::mp11; constexpr auto S = sizeof...(Ts); - using L = mp11::mp_unique>; - using V = mp11::mp_rename; - return mp11::mp_with_index(i, [&](auto I) { return V(std::get(axes)); }); + using V = mp_unique...>>; + return mp_with_index(i, [&](auto I) { return V(std::ref(std::get(axes))); }); } template decltype(auto) axis_get(const std::tuple& axes, unsigned i) { + using namespace boost::mp11; constexpr auto S = sizeof...(Ts); - using L = mp11::mp_unique>; - using V = mp11::mp_rename; - return mp11::mp_with_index(i, [&](auto I) { return V(std::get(axes)); }); + using L = mp_unique...>>; + using V = mp_rename; + return mp_with_index(i, [&](auto I) { return V(std::cref(std::get(axes))); }); } template @@ -84,17 +86,17 @@ bool axes_equal(const std::tuple& ts, const std::tuple& us) { [](const auto&, const auto&) { return false; }, ts, us); } -template -bool axes_equal(const std::tuple& t, const U& u) { - if (sizeof...(Ts) != u.size()) return false; +template +bool axes_equal(const T& t, const std::tuple& u) { + if (t.size() != sizeof...(Us)) return false; bool equal = true; - mp11::mp_for_each>( - [&](auto I) { equal &= u[I] == std::get(t); }); + mp11::mp_for_each>( + [&](auto I) { equal &= t[I] == std::get(u); }); return equal; } -template -bool axes_equal(const T& t, const std::tuple& u) { +template +bool axes_equal(const std::tuple& t, const U& u) { return axes_equal(u, t); } diff --git a/include/boost/histogram/detail/linearize.hpp b/include/boost/histogram/detail/linearize.hpp index 3b90c22f..a7fa96b3 100644 --- a/include/boost/histogram/detail/linearize.hpp +++ b/include/boost/histogram/detail/linearize.hpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/include/boost/histogram/detail/meta.hpp b/include/boost/histogram/detail/meta.hpp index 8523da51..01f8ebf5 100644 --- a/include/boost/histogram/detail/meta.hpp +++ b/include/boost/histogram/detail/meta.hpp @@ -50,6 +50,19 @@ namespace detail { template using remove_cvref_t = std::remove_cv_t>; +template +struct unref_impl { + using type = T; +}; + +template +struct unref_impl> { + using type = T; +}; + +template +using unref_t = typename unref_impl::type; + template using convert_integer = mp11::mp_if>, U, T>; @@ -78,10 +91,6 @@ using arg_type = typename mp11::mp_at_c, N>; template using return_type = typename boost::callable_traits::return_type::type; -template >>> -using visitor_return_type = decltype(std::declval()(std::declval())); - template constexpr T lowest() { return std::numeric_limits::lowest(); diff --git a/include/boost/histogram/detail/try_cast.hpp b/include/boost/histogram/detail/try_cast.hpp index 366e223a..c91c08fe 100644 --- a/include/boost/histogram/detail/try_cast.hpp +++ b/include/boost/histogram/detail/try_cast.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -18,20 +19,28 @@ namespace boost { namespace histogram { namespace detail { template -T try_cast_impl(std::false_type, U&&) { +T try_cast_impl(mp11::mp_int<0>, U&&) { BOOST_THROW_EXCEPTION(E(cat("cannot cast ", type_name(), " to ", type_name()))); } template -T try_cast_impl(std::true_type, U&& u) { +T try_cast_impl(mp11::mp_int<1>, U&& u) { return static_cast(u); } -// cast fails at runtime with exception E instead of compile-time template -T try_cast(U&& u) { - return try_cast_impl(std::is_convertible{}, std::forward(u)); +decltype(auto) try_cast_impl(mp11::mp_int<2>, U&& u) { + return std::forward(u); } + +// cast fails at runtime with exception E instead of compile-time, T must be a value +template +decltype(auto) try_cast(U&& u) { + return try_cast_impl(mp11::mp_int<(std::is_convertible::value + + std::is_same>::value)>{}, + std::forward(u)); +} + } // namespace detail } // namespace histogram } // namespace boost diff --git a/include/boost/histogram/detail/variant.hpp b/include/boost/histogram/detail/variant.hpp new file mode 100644 index 00000000..e4c3e956 --- /dev/null +++ b/include/boost/histogram/detail/variant.hpp @@ -0,0 +1,242 @@ +// Copyright (c) 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) + +#ifndef BOOST_HISTOGRAM_DETAIL_VARIANT_HPP +#define BOOST_HISTOGRAM_DETAIL_VARIANT_HPP + +#include +#include +#include +#include +#include + +namespace boost { +namespace histogram { +namespace detail { + +template +T launder_cast(U&& u) { + return reinterpret_cast(std::forward(u)); +} + +// Simple C++14 variant without external boost dependencies. +// +// * No empty state, first type must have default ctor that never throws; +// if it throws anyway, the program aborts +// * All types must have copy ctors and copy assignment +// * All types must have noexcept move ctors and noexcept move assignment +// * If the current type is a reference, assignment passes through, no rebind +// +template +class variant { + using default_type = mp11::mp_first; + using N = mp11::mp_size; + +public: + variant() noexcept { init_default(); } + + variant(const variant& x) { + x.internal_apply([this, &x](auto i) { + using T = mp11::mp_at_c; + this->init_i(i, *x.ptr(mp11::mp_identity{}, i)); + }); + } + + variant(variant&& x) noexcept { + x.internal_apply([this, &x](auto i) { + using T = mp11::mp_at_c; + this->init_i(i, std::move(*x.ptr(mp11::mp_identity{}, i))); + }); + } + + variant& operator=(const variant& x) { + x.apply([this](const auto& x) { this->operator=(x); }); + return *this; + } + + variant& operator=(variant&& x) noexcept { + x.apply([this](auto&& x) { this->operator=(std::move(x)); }); + return *this; + } + + template ::value>> + explicit variant(U&& x) noexcept { + static_assert(std::is_rvalue_reference::value, ""); + constexpr auto i = find(); + using T = mp11::mp_at_c; + static_assert(std::is_nothrow_move_constructible::value, ""); + init_i(i, std::move(x)); + } + + template ::value>> + explicit variant(const U& x) { + constexpr auto i = find(); + using T = mp11::mp_at_c; + init_i(i, x); + } + + template ::value>> + variant& operator=(U&& x) noexcept { + constexpr auto i = find(); + using T = mp11::mp_at_c; + static_assert(std::is_nothrow_move_constructible::value, ""); + if (i == index_) { + *ptr(mp11::mp_identity{}, i) = std::move(x); + } else { + destroy(); + init_i(i, std::move(x)); + } + return *this; + } + + template ::value>> + variant& operator=(const U& x) { + constexpr auto i = find(); + using T = mp11::mp_at_c; + if (i == index_) { + // nothing to do if T::operator= throws + *ptr(mp11::mp_identity{}, i) = x; + } else { + destroy(); // now in invalid state + try { + // if this throws, need to return to valid state + init_i(i, x); + } catch (...) { + init_default(); + throw; + } + } + return *this; + } + + ~variant() { destroy(); } + + template + bool operator==(const U& x) const noexcept { + constexpr auto i = find(); + static_assert(i < N::value, "argument is not a bounded type"); + using T = mp11::mp_at_c; + return (i == index_) && *ptr(mp11::mp_identity{}, i) == x; + } + + template + bool operator!=(const U& x) const noexcept { + constexpr auto i = find(); + static_assert(i < N::value, "argument is not a bounded type"); + using T = mp11::mp_at_c; + return (i != index_) || *ptr(mp11::mp_identity{}, i) != x; + } + + bool operator==(const variant& x) const noexcept { + return x.apply([this](const auto& x) { return this->operator==(x); }); + } + + bool operator!=(const variant& x) const noexcept { + return x.apply([this](const auto& x) { return this->operator!=(x); }); + } + + unsigned index() const noexcept { return index_; } + + template + T& get() { + T* tp = get_if(); + if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type")); + return *tp; + } + + template + const T& get() const { + const T* tp = get_if(); + if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type")); + return *tp; + } + + template + T* get_if() noexcept { + constexpr auto i = mp11::mp_find{}; + return i == index_ ? ptr(mp11::mp_identity{}, i) : nullptr; + } + + template + const T* get_if() const noexcept { + constexpr auto i = mp11::mp_find{}; + return i == index_ ? ptr(mp11::mp_identity{}, i) : nullptr; + } + + template + decltype(auto) apply(Functor&& functor) const { + return internal_apply([this, &functor](auto i) -> decltype(auto) { + using T = mp11::mp_at_c; + return functor(*(this->ptr(mp11::mp_identity{}, i))); + }); + } + + template + decltype(auto) apply(Functor&& functor) { + return internal_apply([this, &functor](auto i) -> decltype(auto) { + using T = mp11::mp_at_c; + return functor(*(this->ptr(mp11::mp_identity{}, i))); + }); + } + +private: + template + decltype(auto) internal_apply(Functor&& functor) const { + return mp11::mp_with_index(index_, functor); + } + + template + T* ptr(mp11::mp_identity, mp11::mp_size_t) noexcept { + return launder_cast(&buffer_); + } + + template + const T* ptr(mp11::mp_identity, mp11::mp_size_t) const noexcept { + return launder_cast(&buffer_); + } + + void init_default() noexcept { init_i(mp11::mp_size_t<0>{}); } + + template + void init_i(I, Args&&... args) { + new (&buffer_) T(std::forward(args)...); + index_ = I::value; + } + + void destroy() noexcept { + internal_apply([this](auto i) { + using T = mp11::mp_at_c; + this->ptr(mp11::mp_identity{}, i)->~T(); + }); + } + + template + static constexpr auto find() noexcept { + using V = std::decay_t; + return mp11::mp_find{}; + } + + using buffer_t = typename std::aligned_union<0, Ts...>::type; + buffer_t buffer_; + unsigned index_; +}; + +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const variant& x) { + x.apply([&os](const auto& self) { os << self; }); + return os; +} + +// specialization for empty type list, useful for metaprogramming +template <> +class variant<> {}; + +} // namespace detail +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/detail/variant_serialization.hpp b/include/boost/histogram/detail/variant_serialization.hpp new file mode 100644 index 00000000..295a0219 --- /dev/null +++ b/include/boost/histogram/detail/variant_serialization.hpp @@ -0,0 +1,67 @@ +// Copyright 2018 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) +// +// This file is based on boost/serialization/variant.hpp. + +#ifndef BOOST_HISTOGRAM_VARIANT_SERIALIZATION_HPP +#define BOOST_HISTOGRAM_VARIANT_SERIALIZATION_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace serialization { + +template +void save(Archive& ar, histogram::detail::variant const& v, unsigned) { + int which = static_cast(v.index()); + ar << BOOST_SERIALIZATION_NVP(which); + v.apply([&ar](const auto& value) { ar << BOOST_SERIALIZATION_NVP(value); }); +} + +template +void load(Archive& ar, histogram::detail::variant& v, unsigned) { + int which; + ar >> BOOST_SERIALIZATION_NVP(which); + constexpr unsigned N = sizeof...(Ts); + if (which < 0 || static_cast(which) >= N) + // throw on invalid which, which >= 0 can happen if type was removed from variant + boost::serialization::throw_exception(boost::archive::archive_exception( + boost::archive::archive_exception::unsupported_version)); + mp11::mp_with_index(static_cast(which), [&ar, &v](auto I) { + using T = mp11::mp_at_c, I>; + T value; + ar >> BOOST_SERIALIZATION_NVP(value); + v = std::move(value); + T* new_address = &v.template get(); + ar.reset_object_address(new_address, &value); + }); +} + +template +inline void serialize(Archive& ar, histogram::detail::variant& v, + unsigned file_version) { + split_free(ar, v, file_version); +} + +#include + +template +struct tracking_level> { + typedef mpl::integral_c_tag tag; + typedef mpl::int_<::boost::serialization::track_always> type; + BOOST_STATIC_CONSTANT(int, value = type::value); +}; + +} // namespace serialization +} // namespace boost + +#endif // BOOST_HISTOGRAM_VARIANT_SERIALIZATION_HPP diff --git a/include/boost/histogram/histogram.hpp b/include/boost/histogram/histogram.hpp index c6c8849a..d66a753f 100644 --- a/include/boost/histogram/histogram.hpp +++ b/include/boost/histogram/histogram.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/histogram/indexed.hpp b/include/boost/histogram/indexed.hpp index 3272db89..b5146a62 100644 --- a/include/boost/histogram/indexed.hpp +++ b/include/boost/histogram/indexed.hpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include diff --git a/include/boost/histogram/serialization.hpp b/include/boost/histogram/serialization.hpp index ae5f0c64..da0376b1 100644 --- a/include/boost/histogram/serialization.hpp +++ b/include/boost/histogram/serialization.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -138,9 +138,9 @@ void category::serialize(Archive& ar, unsigned /* version */) { ar& serialization::make_nvp("meta", vec_meta_.second()); } -template -template -void variant::serialize(Archive& ar, unsigned /* version */) { +template +void serialize(Archive& ar, variant& v, unsigned /* version */) { + auto& impl = unsafe_access::axis_variant_impl(v); ar& serialization::make_nvp("variant", impl); } } // namespace axis diff --git a/include/boost/histogram/unsafe_access.hpp b/include/boost/histogram/unsafe_access.hpp index 31d26eb9..a4e53fec 100644 --- a/include/boost/histogram/unsafe_access.hpp +++ b/include/boost/histogram/unsafe_access.hpp @@ -18,8 +18,12 @@ namespace histogram { This struct enables access to private data of some classes. It is intended for library developers who need this to implement algorithms efficiently, for example, serialization. Users should not use this. If you are a user who absolutely needs this to - get a specific effect, please submit an issue on Github. This means that the public - interface is insufficient. + get a specific effect, please submit an issue on Github. Perhaps the public + interface is insufficient and should be extended for your use case. + + Unlike the normal interface, the unsafe_access interface may change between versions. + If your code relies on unsafe_access, it may or may not break when you update Boost. + This is another reason to not use it unless you are ok with these conditions. */ struct unsafe_access { /** @@ -91,6 +95,15 @@ struct unsafe_access { static constexpr auto& storage_adaptor_impl(storage_adaptor& storage) { return static_cast::impl_type&>(storage); } + + /** + Get implementation of axis::variant. + @param axis instance of axis::variant. + */ + template + static constexpr auto& axis_variant_impl(Variant& axis) { + return axis.impl; + } }; } // namespace histogram diff --git a/test/Jamfile b/test/Jamfile index 57d359f4..b94065c5 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -34,8 +34,6 @@ project ; alias cxx14 : - [ compile-fail make_histogram_fail0.cpp ] - [ compile-fail make_histogram_fail1.cpp ] [ run algorithm_project_test.cpp ] [ run algorithm_reduce_test.cpp ] [ run algorithm_sum_test.cpp ] @@ -52,6 +50,7 @@ alias cxx14 : [ run detail_iterator_adaptor_test.cpp ] [ run detail_large_int_test.cpp ] [ run detail_linearize_test.cpp ] + [ run detail_variant_test.cpp ] [ run histogram_dynamic_test.cpp ] [ run histogram_growing_test.cpp ] [ run histogram_mixed_test.cpp ] @@ -68,6 +67,12 @@ alias cxx17 : [ run deduction_guides_test.cpp ] : [ requires cpp_deduction_guides ] ; + +# check that useful error messages are produced when library is used incorrectly +alias failure : + [ compile-fail make_histogram_fail0.cpp ] + [ compile-fail make_histogram_fail1.cpp ] + ; alias threading : [ run histogram_threaded_test.cpp ] @@ -83,17 +88,20 @@ alias serialization : [ run storage_adaptor_serialization_test.cpp libserial ] [ run histogram_serialization_test.cpp libserial ] [ run unlimited_storage_serialization_test.cpp libserial ] + [ run detail_variant_serialization_test.cpp libserial ] ; alias libserial : /boost/serialization//boost_serialization : static ; +# "failure" not included in "all", because it is distracting alias all : cxx14 cxx17 threading accumulators range units serialization ; alias minimal : cxx14 cxx17 threading ; explicit cxx14 ; explicit cxx17 ; +explicit failure ; explicit threading ; explicit accumulators ; explicit range ; diff --git a/test/axis_variant_test.cpp b/test/axis_variant_test.cpp index 81ed2474..b1dfc975 100644 --- a/test/axis_variant_test.cpp +++ b/test/axis_variant_test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -22,7 +23,17 @@ using namespace boost::histogram; namespace tr = axis::transform; +namespace std { +template +ostream& operator<<(ostream& os, const reference_wrapper& t) { + os << t.get(); + return os; +} +} // namespace std + int main() { + { (void)axis::variant<>{}; } + { using meta_type = std::vector; auto a = axis::variant, axis::category>{ @@ -50,20 +61,36 @@ int main() { BOOST_TEST_EQ(a.options(), axis::option::overflow_t::value); } - // axis::variant with reference + // axis::variant with std::reference_wrapper { using A = axis::integer<>; - using V = axis::variant; + using B = axis::regular<>; auto a = A(1, 5, "foo"); - V ref(a); - BOOST_TEST_EQ(ref.size(), 4); - BOOST_TEST_EQ(ref.value(0), 1); - BOOST_TEST_EQ(ref.metadata(), a.metadata()); - BOOST_TEST_EQ(ref.options(), a.options()); - // change original through ref - axis::get(ref) = A(7, 14); - BOOST_TEST_EQ(a.size(), 7); - BOOST_TEST_EQ(a.value(0), 7); + auto b = B(3, 1, 5, "bar"); + axis::variant, std::reference_wrapper> r1(std::ref(a)); + BOOST_TEST_EQ(r1, a); + BOOST_TEST_NE(r1, A(2, 4)); + BOOST_TEST_NE(r1, b); + BOOST_TEST_EQ(r1.size(), 4); + BOOST_TEST_EQ(r1.value(0), 1); + BOOST_TEST_EQ(r1.metadata(), a.metadata()); + BOOST_TEST_EQ(r1.options(), a.options()); + // change original through r1 + axis::get(r1).metadata() = "bar"; + BOOST_TEST_EQ(a.metadata(), "bar"); + r1 = std::ref(b); + BOOST_TEST_EQ(r1, b); + + axis::variant, std::reference_wrapper> r2( + std::cref(b)); + BOOST_TEST_EQ(r2, b); + BOOST_TEST_NE(r2, B(4, 1, 5)); + BOOST_TEST_NE(r2, a); + BOOST_TEST_EQ(r2.size(), 3); + BOOST_TEST_EQ(r2.value(0), 1); + BOOST_TEST_EQ(r2.metadata(), "bar"); + b.metadata() = "baz"; + BOOST_TEST_EQ(r2.metadata(), "baz"); } // axis::variant copyable diff --git a/test/detail_meta_test.cpp b/test/detail_meta_test.cpp index fbbe07ca..888e060d 100644 --- a/test/detail_meta_test.cpp +++ b/test/detail_meta_test.cpp @@ -27,11 +27,6 @@ using namespace boost::histogram; using namespace boost::histogram::detail; -struct VisitorTestFunctor { - template - T operator()(T&&); -}; - int main() { // has_method_value* { @@ -310,22 +305,6 @@ int main() { BOOST_TEST_TRAIT_SAME(arg_type, long); } - // visitor_return_type - { - using V1 = axis::variant; - using V2 = axis::variant&; - using V3 = const axis::variant&; - using V4 = axis::variant; - using V5 = axis::variant&; - using V6 = const axis::variant&; - BOOST_TEST_TRAIT_SAME(visitor_return_type, char); - BOOST_TEST_TRAIT_SAME(visitor_return_type, int&); - BOOST_TEST_TRAIT_SAME(visitor_return_type, const long&); - BOOST_TEST_TRAIT_SAME(visitor_return_type, const char&); - BOOST_TEST_TRAIT_SAME(visitor_return_type, const char&); - BOOST_TEST_TRAIT_SAME(visitor_return_type, const char&); - } - // static_if { struct callable { diff --git a/test/detail_misc_test.cpp b/test/detail_misc_test.cpp index 237e09b8..2b84b54a 100644 --- a/test/detail_misc_test.cpp +++ b/test/detail_misc_test.cpp @@ -41,8 +41,11 @@ int main() { auto a1 = axis::integer<>(0, 1); auto a2 = axis::integer<>(1, 2); auto tup = std::make_tuple(a1, a2); - using E1 = axis::variant&>; + using E1 = axis::variant>>; BOOST_TEST_TRAIT_SAME(decltype(detail::axis_get(tup, 0)), E1); + BOOST_TEST_EQ(detail::axis_get(tup, 0), a1); + BOOST_TEST_EQ(detail::axis_get(tup, 1), a2); + BOOST_TEST_NE(detail::axis_get(tup, 0), a2); } // sequence equality diff --git a/test/detail_variant_serialization_test.cpp b/test/detail_variant_serialization_test.cpp new file mode 100644 index 00000000..57e41fc3 --- /dev/null +++ b/test/detail_variant_serialization_test.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 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) + +// This test is inspired by the corresponding boost/beast test of detail_variant. + +#include +#include +#include +#include "utility_serialization.hpp" + +using namespace boost::histogram::detail; + +int main() { + const char* filename = XML_PATH "detail_variant_serialization_test.xml"; + + { + variant a(1.0); + print_xml(filename, a); + + variant b(42); + BOOST_TEST_NE(a, b); + load_xml(filename, b); + BOOST_TEST_EQ(a, b); + + variant c; // load incompatible version + BOOST_TEST_THROWS(load_xml(filename, c), boost::archive::archive_exception); + } + + return boost::report_errors(); +} diff --git a/test/detail_variant_serialization_test.xml b/test/detail_variant_serialization_test.xml new file mode 100644 index 00000000..21ba09eb --- /dev/null +++ b/test/detail_variant_serialization_test.xml @@ -0,0 +1,8 @@ + + + + + 1 + 1.00000000000000000e+00 + + \ No newline at end of file diff --git a/test/detail_variant_test.cpp b/test/detail_variant_test.cpp new file mode 100644 index 00000000..823137b8 --- /dev/null +++ b/test/detail_variant_test.cpp @@ -0,0 +1,238 @@ +// Copyright (c) 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) + +// This test is inspired by the corresponding boost/beast test of detail_variant. + +#include +#include +#include +#include +#include +#include + +using namespace boost::histogram::detail; +using namespace std::literals; + +template +struct Q { + Q() noexcept { ++Q::count; } + Q(const Q& q) : data(q.data) { + if (q.data == 0xBAD) // simulate failing copy ctor + throw std::bad_alloc{}; + ++Q::count; + } + Q(Q&& q) noexcept { + data = q.data; + moved = true; + ++Q::count; + } + Q& operator=(const Q& q) { + if (q.data == 0xBAD) // simulate failing copy ctor + throw std::bad_alloc{}; + data = q.data; + return *this; + } + Q& operator=(Q&& q) noexcept { + data = q.data; + moved = true; + return *this; + } + ~Q() { --Q::count; } + + Q(int x) : Q() { data = x; } + + operator int() const noexcept { return data; } + + int data; + bool moved = false; + static int count; +}; + +template +int Q::count = 0; + +int main() { + // test Q + BOOST_TEST_EQ(Q<1>::count, 0); + { + Q<1> q(5); + BOOST_TEST_EQ(q, 5); + BOOST_TEST_EQ(Q<1>::count, 1); + Q<1> q2(q); + BOOST_TEST_EQ(q2, 5); + BOOST_TEST_NOT(q2.moved); + Q<1> q3(std::move(q)); + BOOST_TEST_EQ(q3, 5); + BOOST_TEST(q3.moved); + Q<1> q4; + q4 = Q<1>(3); + BOOST_TEST_EQ(q4, 3); + BOOST_TEST(q4.moved); + Q<1> q5(0xBAD); // ok + BOOST_TEST_THROWS((Q<1>(q5)), std::bad_alloc); + } + BOOST_TEST_EQ(Q<1>::count, 0); + + // default ctor and dtor + { + variant> v; + BOOST_TEST_EQ(v.index(), 0); + BOOST_TEST_EQ(Q<1>::count, 1); + + variant<> v2; + (void)v2; + } + BOOST_TEST_EQ(Q<1>::count, 0); + + // copy ctor + { + using V = variant, Q<2>>; + Q<1> q1{5}; + const V v1(q1); + BOOST_TEST_EQ(Q<1>::count, 2); + BOOST_TEST_EQ(Q<2>::count, 0); + BOOST_TEST_EQ(v1.index(), 0); + BOOST_TEST_EQ(v1, q1); + BOOST_TEST_NOT(v1.get>().moved); + const Q<2> q2{3}; + V v2(q2); + BOOST_TEST_EQ(Q<1>::count, 2); + BOOST_TEST_EQ(Q<2>::count, 2); + BOOST_TEST_EQ(v2.index(), 1); + BOOST_TEST_EQ(v2, q2); + BOOST_TEST_NOT(v2.get>().moved); + V v3(v1); + BOOST_TEST_EQ(v3.index(), 0); + BOOST_TEST_EQ(v3, q1); + BOOST_TEST_EQ(Q<1>::count, 3); + BOOST_TEST_EQ(Q<2>::count, 2); + BOOST_TEST_NOT(v3.get>().moved); + Q<1> q4(0xBAD); + BOOST_TEST_THROWS((V(q4)), std::bad_alloc); + } + BOOST_TEST_EQ(Q<1>::count, 0); + BOOST_TEST_EQ(Q<2>::count, 0); + + // move ctor + { + using V = variant, Q<2>>; + V v1(Q<1>{5}); + BOOST_TEST_EQ(v1.index(), 0); + BOOST_TEST(v1.get>().moved); + BOOST_TEST_EQ(v1, Q<1>{5}); + V v2(Q<2>{3}); + BOOST_TEST_EQ(v2.index(), 1); + BOOST_TEST(v2.get>().moved); + BOOST_TEST_EQ(v2, Q<2>{3}); + Q<1> q{4}; + V v3(q); + BOOST_TEST_NOT(v3.get>().moved); + V v4(std::move(v3)); + BOOST_TEST(v4.get>().moved); + } + BOOST_TEST_EQ(Q<1>::count, 0); + BOOST_TEST_EQ(Q<2>::count, 0); + + // move assign + { + using V = variant, Q<2>>; + V v; + BOOST_TEST_EQ(v.index(), 0); + BOOST_TEST_NOT(v.get>().moved); + v = Q<1>{5}; + BOOST_TEST_EQ(v.index(), 0); + BOOST_TEST_EQ(v, Q<1>{5}); + BOOST_TEST(v.get>().moved); + BOOST_TEST_EQ(Q<1>::count, 1); + BOOST_TEST_EQ(Q<2>::count, 0); + v = Q<2>{3}; + BOOST_TEST_EQ(v.index(), 1); + BOOST_TEST_EQ(v, Q<2>{3}); + BOOST_TEST(v.get>().moved); + BOOST_TEST_EQ(Q<1>::count, 0); + BOOST_TEST_EQ(Q<2>::count, 1); + } + BOOST_TEST_EQ(Q<1>::count, 0); + BOOST_TEST_EQ(Q<2>::count, 0); + + // copy assign + { + using V = variant, Q<2>, Q<3>>; + V v; + BOOST_TEST_EQ(v.index(), 0); + Q<1> q{3}; + v = q; + BOOST_TEST_EQ(v.index(), 0); + BOOST_TEST_EQ(v, q); + BOOST_TEST_NOT(v.get>().moved); + BOOST_TEST_EQ(Q<1>::count, 2); + BOOST_TEST_EQ(Q<2>::count, 0); + Q<2> q2(5); + v = q2; + BOOST_TEST_EQ(v.index(), 1); + BOOST_TEST_EQ(v, q2); + BOOST_TEST_NOT(v.get>().moved); + BOOST_TEST_EQ(Q<1>::count, 1); + BOOST_TEST_EQ(Q<2>::count, 2); + + BOOST_TEST_EQ(v.index(), 1); + Q<3> q3(0xBAD); + BOOST_TEST_THROWS(v = q3, std::bad_alloc); + BOOST_TEST_EQ(v.index(), 0); // is now in default state + } + BOOST_TEST_EQ(Q<1>::count, 0); + BOOST_TEST_EQ(Q<2>::count, 0); + + // get + { + variant, Q<2>> v; + v = Q<1>(1); + BOOST_TEST_EQ(v.get>(), 1); + BOOST_TEST_THROWS(v.get>(), std::runtime_error); + + const auto& crv = v; + BOOST_TEST_EQ(crv.get>(), 1); + BOOST_TEST_THROWS(crv.get>(), std::runtime_error); + + auto p1 = v.get_if>(); + BOOST_TEST(p1 && *p1 == 1); + p1->data = 3; + + auto p2 = crv.get_if>(); + BOOST_TEST(p2 && *p2 == 3); + + BOOST_TEST_NOT(v.get_if>()); + BOOST_TEST_NOT(v.get_if()); + BOOST_TEST_NOT(crv.get_if>()); + BOOST_TEST_NOT(crv.get_if()); + } + + // apply + { + variant, Q<2>> v; + v = Q<1>(1); + v.apply([](auto& x) { + BOOST_TEST_EQ(x, 1); + BOOST_TEST_TRAIT_SAME(decltype(x), Q<1>&); + }); + v = Q<2>(2); + const auto& crv = v; + crv.apply([](const auto& x) { + BOOST_TEST_EQ(x, 2); + BOOST_TEST_TRAIT_SAME(decltype(x), const Q<2>&); + }); + } + + // ostream + { + std::ostringstream os; + variant, Q<2>> v(Q<1>{3}); + os << v; + BOOST_TEST_EQ(os.str(), "3"s); + } + + return boost::report_errors(); +} diff --git a/tools/cov.sh b/tools/cov.sh index 5c6328b7..bab03afa 100755 --- a/tools/cov.sh +++ b/tools/cov.sh @@ -4,7 +4,7 @@ if [ -z $GCOV ]; then GCOV=gcov fi -LCOV_VERSION="1.13" +LCOV_VERSION="1.14" LCOV_DIR="tools/lcov-${LCOV_VERSION}" if [ ! -e $LCOV_DIR ]; then