From 3aa1ed326dc69b524e2610fb06d9abc074b89c68 Mon Sep 17 00:00:00 2001 From: Alex Shabalin Date: Wed, 11 Sep 2024 15:56:35 +0200 Subject: [PATCH 1/2] Make persist compatible with C++17 --- doc/persist-introduction.rst | 2 +- extra/fuzzer/persist/CMakeLists.txt | 2 + extra/fuzzer/persist/flex-vector.cpp | 3 +- flake.nix | 2 +- immer/extra/persist/cereal/archives.hpp | 8 +-- immer/extra/persist/cereal/load.hpp | 13 ++-- immer/extra/persist/cereal/policy.hpp | 53 ++++++++------- immer/extra/persist/cereal/save.hpp | 21 +++--- immer/extra/persist/detail/alias.hpp | 11 ++- immer/extra/persist/detail/array/pool.hpp | 12 +++- immer/extra/persist/detail/box/pool.hpp | 12 +++- .../detail/cereal/input_archive_util.hpp | 4 +- .../persist/detail/cereal/persistable.hpp | 11 ++- immer/extra/persist/detail/cereal/pools.hpp | 12 +++- immer/extra/persist/detail/cereal/wrap.hpp | 6 +- immer/extra/persist/detail/champ/champ.hpp | 1 - immer/extra/persist/detail/champ/input.hpp | 4 +- immer/extra/persist/detail/champ/output.hpp | 12 ++-- immer/extra/persist/detail/champ/pool.hpp | 31 ++++++++- immer/extra/persist/detail/champ/traits.hpp | 1 - immer/extra/persist/detail/common/pool.hpp | 14 ++-- immer/extra/persist/detail/names.hpp | 6 +- immer/extra/persist/detail/rbts/input.hpp | 3 - immer/extra/persist/detail/rbts/output.hpp | 12 ++-- immer/extra/persist/detail/rbts/pool.hpp | 32 +++++++-- immer/extra/persist/detail/type_traverse.hpp | 13 ++-- immer/extra/persist/transform.hpp | 2 +- justfile | 4 +- test/extra/persist/CMakeLists.txt | 7 +- test/extra/persist/test_array.cpp | 3 +- test/extra/persist/test_champ.cpp | 3 +- .../test_circular_dependency_conversion.cpp | 13 +++- test/extra/persist/test_for_docs.cpp | 68 +++++++++++++++---- test/extra/persist/test_special_pool_auto.cpp | 28 +++++--- test/extra/persist/test_vectors.cpp | 14 ++-- test/extra/persist/utils.hpp | 12 ++-- 36 files changed, 308 insertions(+), 147 deletions(-) diff --git a/doc/persist-introduction.rst b/doc/persist-introduction.rst index 04289bfa..e47bce14 100644 --- a/doc/persist-introduction.rst +++ b/doc/persist-introduction.rst @@ -15,7 +15,7 @@ Dependencies ------------ In addition to the `dependencies `_ of -``immer``, this library makes use of **C++20**, `Boost.Hana +``immer``, this library makes use of **C++17**, `Boost.Hana `_, `fmt `_ and `cereal `_. diff --git a/extra/fuzzer/persist/CMakeLists.txt b/extra/fuzzer/persist/CMakeLists.txt index fc8b4e46..9e98b9e1 100644 --- a/extra/fuzzer/persist/CMakeLists.txt +++ b/extra/fuzzer/persist/CMakeLists.txt @@ -6,6 +6,8 @@ foreach(_file IN LISTS immer_fuzzers) add_executable(${_target} EXCLUDE_FROM_ALL "${_file}") set_target_properties(${_target} PROPERTIES OUTPUT_NAME ${_output}) target_compile_options(${_target} PUBLIC ${immer_fuzzing_engine}) + target_compile_options(${_target} PRIVATE ${immer_fuzzing_engine} + -Wno-c++20-designator) target_include_directories(${_target} PRIVATE ../..) target_link_libraries(${_target} PUBLIC ${immer_fuzzing_engine} immer-dev) add_dependencies(fuzzers ${_target}) diff --git a/extra/fuzzer/persist/flex-vector.cpp b/extra/fuzzer/persist/flex-vector.cpp index 9e41c139..b98f8e47 100644 --- a/extra/fuzzer/persist/flex-vector.cpp +++ b/extra/fuzzer/persist/flex-vector.cpp @@ -18,7 +18,8 @@ #include namespace { -void require_eq(const auto& a, const auto& b) +template +void require_eq(const T& a, const T& b) { if (a != b) { throw std::runtime_error{ diff --git a/flake.nix b/flake.nix index 94750fab..02c34312 100644 --- a/flake.nix +++ b/flake.nix @@ -200,7 +200,7 @@ "-Dimmer_BUILD_TESTS=ON" "-Dimmer_BUILD_PERSIST_TESTS=ON" "-Dimmer_BUILD_EXAMPLES=OFF" - "-DCXX_STANDARD=20" + "-DCXX_STANDARD=17" ]; }); }; diff --git a/immer/extra/persist/cereal/archives.hpp b/immer/extra/persist/cereal/archives.hpp index 6d311738..70ce7ad0 100644 --- a/immer/extra/persist/cereal/archives.hpp +++ b/immer/extra/persist/cereal/archives.hpp @@ -60,7 +60,10 @@ struct blackhole_output_archive // blackhole_output_archive doesn't care about names struct empty_name_fn { - void operator()(const auto& container) const {} + template + void operator()(const T& container) const + { + } }; template @@ -99,7 +102,6 @@ class output_pools_cereal_archive_wrapper template explicit output_pools_cereal_archive_wrapper(Args&&... args) - requires std::is_same_v : cereal::OutputArchive{this} , previous{std::forward(args)...} { @@ -107,7 +109,6 @@ class output_pools_cereal_archive_wrapper template output_pools_cereal_archive_wrapper(Pools pools_, Args&&... args) - requires std::is_same_v : cereal::OutputArchive{this} , previous{std::forward(args)...} , pools{std::move(pools_)} @@ -279,7 +280,6 @@ class input_pools_cereal_archive_wrapper template input_pools_cereal_archive_wrapper(Pools pools_, Args&&... args) - requires std::is_same_v : cereal::InputArchive{this} , previous{std::forward(args)...} , pools{std::move(pools_)} diff --git a/immer/extra/persist/cereal/load.hpp b/immer/extra/persist/cereal/load.hpp index 7ff99835..2a57a76f 100644 --- a/immer/extra/persist/cereal/load.hpp +++ b/immer/extra/persist/cereal/load.hpp @@ -15,8 +15,8 @@ namespace immer::persist { * @ingroup persist-api */ template Policy = default_policy, + class Archive = cereal::JSONInputArchive, + class Policy = default_policy, class... Args> T cereal_load_with_pools(std::istream& is, const Policy& policy = Policy{}, @@ -26,10 +26,7 @@ T cereal_load_with_pools(std::istream& is, decltype(boost::hana::to_set(policy.get_pool_types(std::declval()))); using Pools = decltype(detail::generate_input_pools(TypesSet{})); - auto get_pool_name_fn = [](const auto& value) { - return Policy{}.get_pool_name(value); - }; - using PoolNameFn = decltype(get_pool_name_fn); + using PoolNameFn = get_pool_name_fn_t; const auto wrap = detail::wrap_known_types(TypesSet{}, detail::wrap_for_loading); @@ -53,8 +50,8 @@ T cereal_load_with_pools(std::istream& is, * @ingroup persist-api */ template Policy = default_policy> + class Archive = cereal::JSONInputArchive, + class Policy = default_policy> T cereal_load_with_pools(const std::string& input, const Policy& policy = Policy{}) { diff --git a/immer/extra/persist/cereal/policy.hpp b/immer/extra/persist/cereal/policy.hpp index 562b4454..c6330fe3 100644 --- a/immer/extra/persist/cereal/policy.hpp +++ b/immer/extra/persist/cereal/policy.hpp @@ -9,24 +9,6 @@ namespace immer::persist { * @defgroup persist-policy */ -/** - * @brief Policy is a type that describes certain aspects of serialization for - * `immer::persist`. - * - How to call into the `cereal` archive to save and load the - * user-provided value. Can be used to serealize the value inline (without the - * `value0` node) by taking a dependency on cereal-inline, for - * example. - * - Types of `immer` containers that will be serialized using pools. One - * pool contains nodes of only one `immer` container type. - * - Names for each per-type pool. - * - * @ingroup persist-policy - */ -template -concept Policy = - requires(Value value, T policy) { policy.get_pool_types(value); }; - template auto get_pools_names(const T&) { @@ -67,6 +49,20 @@ struct value0_serialize_t } }; +/** + * @brief Policy is a type that describes certain aspects of serialization for + * `immer::persist`. + * - How to call into the `cereal` archive to save and load the + * user-provided value. Can be used to serealize the value inline (without the + * `value0` node) by taking a dependency on cereal-inline, for + * example. + * - Types of `immer` containers that will be serialized using pools. One + * pool contains nodes of only one `immer` container type. + * - Names for each per-type pool. + * + * @ingroup persist-policy + */ template struct via_get_pools_names_policy_t : value0_serialize_t { @@ -100,9 +96,10 @@ struct via_get_pools_names_policy_t : value0_serialize_t * * @ingroup persist-policy */ -auto via_get_pools_names_policy(const auto& value) +template +auto via_get_pools_names_policy(const T& value) { - return via_get_pools_names_policy_t>{}; + return via_get_pools_names_policy_t>{}; } /** @@ -187,12 +184,22 @@ struct hana_struct_auto_member_name_policy_t : value0_serialize_t * * @ingroup persist-policy */ -auto hana_struct_auto_member_name_policy(const auto& value) +template +auto hana_struct_auto_member_name_policy(const T& value) { - return hana_struct_auto_member_name_policy_t< - std::decay_t>{}; + return hana_struct_auto_member_name_policy_t>{}; } using default_policy = via_get_pools_types_policy; +template +struct get_pool_name_fn_t +{ + template + auto operator()(const T& value) const + { + return Policy{}.get_pool_name(value); + } +}; + } // namespace immer::persist diff --git a/immer/extra/persist/cereal/save.hpp b/immer/extra/persist/cereal/save.hpp index e37945ff..ee64722b 100644 --- a/immer/extra/persist/cereal/save.hpp +++ b/immer/extra/persist/cereal/save.hpp @@ -20,7 +20,7 @@ namespace immer::persist { */ template Policy = default_policy, + class Policy = default_policy, class... Args> void cereal_save_with_pools(std::ostream& os, const T& value0, @@ -31,14 +31,11 @@ void cereal_save_with_pools(std::ostream& os, auto pools = detail::generate_output_pools(types); const auto wrap = detail::wrap_known_types(types, detail::wrap_for_saving); using Pools = std::decay_t; - auto get_pool_name_fn = [](const auto& value) { - return Policy{}.get_pool_name(value); - }; - auto ar = immer::persist::output_pools_cereal_archive_wrapper< + auto ar = immer::persist::output_pools_cereal_archive_wrapper< Archive, Pools, decltype(wrap), - decltype(get_pool_name_fn)>{ + get_pool_name_fn_t>{ pools, wrap, os, std::forward(args)...}; policy.save(ar, value0); // Calling finalize explicitly, as it might throw on saving the pools, @@ -54,16 +51,18 @@ void cereal_save_with_pools(std::ostream& os, * @return std::string The resulting JSON. * @ingroup persist-api */ -template Policy = default_policy, - class... Args> +template < + class Archive = cereal::JSONOutputArchive, + class T, + class Policy = default_policy, + class = decltype(std::declval().get_pool_types(std::declval())), + class... Args> std::string cereal_save_with_pools(const T& value0, const Policy& policy = Policy{}, Args&&... args) { auto os = std::ostringstream{}; - cereal_save_with_pools( + cereal_save_with_pools( os, value0, policy, std::forward(args)...); return os.str(); } diff --git a/immer/extra/persist/detail/alias.hpp b/immer/extra/persist/detail/alias.hpp index 38b251f0..67a4acec 100644 --- a/immer/extra/persist/detail/alias.hpp +++ b/immer/extra/persist/detail/alias.hpp @@ -18,8 +18,15 @@ struct type_alias { } - friend bool operator==(const type_alias& left, - const type_alias& right) = default; + friend bool operator==(const type_alias& left, const type_alias& right) + { + return left.value == right.value; + } + + friend bool operator!=(const type_alias& left, const type_alias& right) + { + return left.value != right.value; + } /** * This works only starting with fmt v10. diff --git a/immer/extra/persist/detail/array/pool.hpp b/immer/extra/persist/detail/array/pool.hpp index 5cee43a3..0cf5ffd4 100644 --- a/immer/extra/persist/detail/array/pool.hpp +++ b/immer/extra/persist/detail/array/pool.hpp @@ -22,6 +22,11 @@ struct output_pool return left.arrays == right.arrays; } + friend bool operator!=(const output_pool& left, const output_pool& right) + { + return left.arrays != right.arrays; + } + template void save(Archive& ar) const { @@ -78,8 +83,10 @@ struct input_pool { immer::vector> arrays; - friend bool operator==(const input_pool& left, - const input_pool& right) = default; + friend bool operator==(const input_pool& left, const input_pool& right) + { + return left.arrays == right.arrays; + } template void load(Archive& ar) @@ -125,7 +132,6 @@ class loader { public: explicit loader(Pool pool) - requires std::is_same_v : pool_{std::move(pool)} { } diff --git a/immer/extra/persist/detail/box/pool.hpp b/immer/extra/persist/detail/box/pool.hpp index 83a90a2f..ddb7df49 100644 --- a/immer/extra/persist/detail/box/pool.hpp +++ b/immer/extra/persist/detail/box/pool.hpp @@ -26,6 +26,11 @@ struct output_pool return left.boxes == right.boxes; } + friend bool operator!=(const output_pool& left, const output_pool& right) + { + return left.boxes != right.boxes; + } + template void save(Archive& ar) const { @@ -64,8 +69,10 @@ struct input_pool { immer::vector> boxes; - friend bool operator==(const input_pool& left, - const input_pool& right) = default; + friend bool operator==(const input_pool& left, const input_pool& right) + { + return left.boxes == right.boxes; + } template void load(Archive& ar) @@ -141,7 +148,6 @@ class loader { public: explicit loader(Pool pool) - requires std::is_same_v : pool_{std::move(pool)} { } diff --git a/immer/extra/persist/detail/cereal/input_archive_util.hpp b/immer/extra/persist/detail/cereal/input_archive_util.hpp index af2decbd..52ac24df 100644 --- a/immer/extra/persist/detail/cereal/input_archive_util.hpp +++ b/immer/extra/persist/detail/cereal/input_archive_util.hpp @@ -5,8 +5,8 @@ namespace immer::persist::detail { -template -auto load_pools(std::istream& is, const auto& wrap) +template +auto load_pools(std::istream& is, const WrapF& wrap) { const auto reload_pool = [wrap](std::istream& is, Pools pools, bool ignore_pool_exceptions) { diff --git a/immer/extra/persist/detail/cereal/persistable.hpp b/immer/extra/persist/detail/cereal/persistable.hpp index d0946110..30e65b10 100644 --- a/immer/extra/persist/detail/cereal/persistable.hpp +++ b/immer/extra/persist/detail/cereal/persistable.hpp @@ -51,8 +51,15 @@ struct persistable { } - friend bool operator==(const persistable& left, - const persistable& right) = default; + friend bool operator==(const persistable& left, const persistable& right) + { + return left.container == right.container; + } + + friend bool operator!=(const persistable& left, const persistable& right) + { + return left.container != right.container; + } }; template : pool{std::move(pool_)} { } @@ -201,6 +200,11 @@ struct input_pool return left.pool == right.pool; } + friend bool operator!=(const input_pool& left, const input_pool& right) + { + return left.pool != right.pool; + } + void merge_previous(const input_pool& original) { pool.merge_previous(original.pool); @@ -429,7 +433,8 @@ class input_pools } }; -inline auto generate_output_pools(auto types) +template +auto generate_output_pools(T types) { auto storage = hana::fold_left(types, hana::make_map(), [](auto map, auto type) { @@ -444,7 +449,8 @@ inline auto generate_output_pools(auto types) return output_pools{storage}; } -inline auto generate_input_pools(auto types) +template +auto generate_input_pools(T types) { auto storage = hana::fold_left(types, hana::make_map(), [](auto map, auto type) { diff --git a/immer/extra/persist/detail/cereal/wrap.hpp b/immer/extra/persist/detail/cereal/wrap.hpp index 3b5d4b07..641dbfb5 100644 --- a/immer/extra/persist/detail/cereal/wrap.hpp +++ b/immer/extra/persist/detail/cereal/wrap.hpp @@ -77,10 +77,10 @@ constexpr auto wrap_for_loading = /** * Returns a wrapping function that wraps only known types. */ -inline auto wrap_known_types(auto types, auto wrap) +template +auto wrap_known_types(KnownSet types, WrapF wrap) { - static_assert(boost::hana::is_a); - using KnownSet = decltype(types); + static_assert(boost::hana::is_a); const auto is_known = [](const auto& value) { using result_t = decltype(boost::hana::contains( KnownSet{}, boost::hana::typeid_(value))); diff --git a/immer/extra/persist/detail/champ/champ.hpp b/immer/extra/persist/detail/champ/champ.hpp index f8757276..f9d87287 100644 --- a/immer/extra/persist/detail/champ/champ.hpp +++ b/immer/extra/persist/detail/champ/champ.hpp @@ -75,7 +75,6 @@ class container_loader public: explicit container_loader(Pool pool) - requires std::is_same_v : pool_{std::move(pool)} , nodes_{pool_.nodes} { diff --git a/immer/extra/persist/detail/champ/input.hpp b/immer/extra/persist/detail/champ/input.hpp index f01e4e71..8ee3b694 100644 --- a/immer/extra/persist/detail/champ/input.hpp +++ b/immer/extra/persist/detail/champ/input.hpp @@ -67,7 +67,6 @@ class nodes_loader using values_t = immer::flex_vector>; explicit nodes_loader(NodesLoad pool) - requires std::is_same_v : pool_{std::move(pool)} { } @@ -226,7 +225,8 @@ class nodes_loader } private: - immer::array get_values(const auto& array) const + template + immer::array get_values(const Array& array) const { if constexpr (std::is_same_v) { return array; diff --git a/immer/extra/persist/detail/champ/output.hpp b/immer/extra/persist/detail/champ/output.hpp index b8f8f8ee..42b82309 100644 --- a/immer/extra/persist/detail/champ/output.hpp +++ b/immer/extra/persist/detail/champ/output.hpp @@ -9,7 +9,8 @@ struct output_pool_builder { nodes_save pool; - void visit_inner(const auto* node, auto depth) + template + void visit_inner(const Node* node, Depth depth) { auto id = get_node_id(node); if (pool.inners.count(id)) { @@ -38,7 +39,8 @@ struct output_pool_builder pool.inners = std::move(pool.inners).set(id, node_info); } - void visit_collision(const auto* node) + template + void visit_collision(const Node* node) { auto id = get_node_id(node); if (pool.inners.count(id)) { @@ -55,7 +57,8 @@ struct output_pool_builder }); } - void visit(const auto* node, immer::detail::hamts::count_t depth) + template + void visit(const Node* node, immer::detail::hamts::count_t depth) { using immer::detail::hamts::max_depth; @@ -66,7 +69,8 @@ struct output_pool_builder } } - node_id get_node_id(auto* ptr) + template + node_id get_node_id(Node* ptr) { auto [pool2, id] = immer::persist::champ::get_node_id(std::move(pool), ptr); diff --git a/immer/extra/persist/detail/champ/pool.hpp b/immer/extra/persist/detail/champ/pool.hpp index 9a899601..eebb1820 100644 --- a/immer/extra/persist/detail/champ/pool.hpp +++ b/immer/extra/persist/detail/champ/pool.hpp @@ -24,8 +24,16 @@ struct inner_node_save bitmap_t datamap; bool collisions{false}; + auto tie() const + { + return std::tie(values, children, nodemap, datamap, collisions); + } + friend bool operator==(const inner_node_save& left, - const inner_node_save& right) = default; + const inner_node_save& right) + { + return left.tie() == right.tie(); + } template void save(Archive& ar) const @@ -49,8 +57,16 @@ struct inner_node_load bitmap_t datamap; bool collisions{false}; + auto tie() const + { + return std::tie(values, children, nodemap, datamap, collisions); + } + friend bool operator==(const inner_node_load& left, - const inner_node_load& right) = default; + const inner_node_load& right) + { + return left.tie() == right.tie(); + } template void load(Archive& ar) @@ -145,6 +161,12 @@ struct container_output_pool return left.nodes == right.nodes; } + friend bool operator!=(const container_output_pool& left, + const container_output_pool& right) + { + return left.nodes != right.nodes; + } + template void save(Archive& ar) const { @@ -164,7 +186,10 @@ struct container_input_pool nodes_load nodes; friend bool operator==(const container_input_pool& left, - const container_input_pool& right) = default; + const container_input_pool& right) + { + return left.nodes == right.nodes; + } template void load(Archive& ar) diff --git a/immer/extra/persist/detail/champ/traits.hpp b/immer/extra/persist/detail/champ/traits.hpp index ef8d46fb..3bb16e02 100644 --- a/immer/extra/persist/detail/champ/traits.hpp +++ b/immer/extra/persist/detail/champ/traits.hpp @@ -24,7 +24,6 @@ struct champ_traits template static auto transform(F&& func) - requires std::is_invocable_v { // We need this special target_container_type_request because we can't // determine the hash and equality operators for the new key any other diff --git a/immer/extra/persist/detail/common/pool.hpp b/immer/extra/persist/detail/common/pool.hpp index 0fb6ede4..2692ba5d 100644 --- a/immer/extra/persist/detail/common/pool.hpp +++ b/immer/extra/persist/detail/common/pool.hpp @@ -19,8 +19,12 @@ struct values_save const T* begin = nullptr; const T* end = nullptr; - friend bool operator==(const values_save& left, - const values_save& right) = default; + auto tie() const { return std::tie(begin, end); } + + friend bool operator==(const values_save& left, const values_save& right) + { + return left.tie() == right.tie(); + } }; /** @@ -44,8 +48,10 @@ struct values_load { } - friend bool operator==(const values_load& left, - const values_load& right) = default; + friend bool operator==(const values_load& left, const values_load& right) + { + return left.data == right.data; + } }; template diff --git a/immer/extra/persist/detail/names.hpp b/immer/extra/persist/detail/names.hpp index 7126837d..f0e1e5a4 100644 --- a/immer/extra/persist/detail/names.hpp +++ b/immer/extra/persist/detail/names.hpp @@ -20,7 +20,8 @@ class error_duplicate_pool_name_found; * * @ingroup persist-impl */ -inline auto are_type_names_unique(auto type_names) +template +auto are_type_names_unique(T type_names) { namespace hana = boost::hana; auto names_set = @@ -44,7 +45,8 @@ struct name_from_map_fn static_assert(decltype(are_type_names_unique(Map{}))::value, "Pool names in the map must be unique"); - auto operator()(const auto& container) const + template + auto operator()(const T& container) const { return Map{}[boost::hana::typeid_(container)].c_str(); } diff --git a/immer/extra/persist/detail/rbts/input.hpp b/immer/extra/persist/detail/rbts/input.hpp index a6541e04..6f9e8260 100644 --- a/immer/extra/persist/detail/rbts/input.hpp +++ b/immer/extra/persist/detail/rbts/input.hpp @@ -109,7 +109,6 @@ class loader using nodes_set_t = immer::set; explicit loader(Pool pool) - requires std::is_same_v : pool_{std::move(pool)} { } @@ -534,7 +533,6 @@ class vector_loader { public: explicit vector_loader(Pool pool) - requires std::is_same_v : loader{std::move(pool)} { } @@ -571,7 +569,6 @@ class flex_vector_loader { public: explicit flex_vector_loader(Pool pool) - requires std::is_same_v : loader{std::move(pool)} { } diff --git a/immer/extra/persist/detail/rbts/output.hpp b/immer/extra/persist/detail/rbts/output.hpp index e85dcdac..be78beed 100644 --- a/immer/extra/persist/detail/rbts/output.hpp +++ b/immer/extra/persist/detail/rbts/output.hpp @@ -33,8 +33,8 @@ struct pool_builder { output_pool pool; - template - void operator()(regular_pos_tag, Pos& pos, auto&& visit) + template + void operator()(regular_pos_tag, Pos& pos, VisitF&& visit) { auto id = get_node_id(pos.node()); if (pool.inners.count(id)) { @@ -54,8 +54,8 @@ struct pool_builder pool.inners = std::move(pool.inners).set(id, node_info); } - template - void operator()(relaxed_pos_tag, Pos& pos, auto&& visit) + template + void operator()(relaxed_pos_tag, Pos& pos, VisitF&& visit) { auto id = get_node_id(pos.node()); if (pool.inners.count(id)) { @@ -78,8 +78,8 @@ struct pool_builder pool.inners = std::move(pool.inners).set(id, node_info); } - template - void operator()(leaf_pos_tag, Pos& pos, auto&& visit) + template + void operator()(leaf_pos_tag, Pos& pos, VisitF&& visit) { T* first = pos.node()->leaf(); auto id = get_node_id(pos.node()); diff --git a/immer/extra/persist/detail/rbts/pool.hpp b/immer/extra/persist/detail/rbts/pool.hpp index 829d7adc..0c828f25 100644 --- a/immer/extra/persist/detail/rbts/pool.hpp +++ b/immer/extra/persist/detail/rbts/pool.hpp @@ -20,8 +20,12 @@ struct inner_node immer::vector children; bool relaxed = {}; - friend bool operator==(const inner_node& left, - const inner_node& right) = default; + auto tie() const { return std::tie(children, relaxed); } + + friend bool operator==(const inner_node& left, const inner_node& right) + { + return left.tie() == right.tie(); + } template void serialize(Archive& ar) @@ -35,8 +39,12 @@ struct rbts_info node_id root; node_id tail; - friend bool operator==(const rbts_info& left, - const rbts_info& right) = default; + auto tie() const { return std::tie(root, tail); } + + friend bool operator==(const rbts_info& left, const rbts_info& right) + { + return left.tie() == right.tie(); + } template void serialize(Archive& ar) @@ -70,6 +78,11 @@ struct output_pool return left.tie() == right.tie(); } + friend bool operator!=(const output_pool& left, const output_pool& right) + { + return left.tie() != right.tie(); + } + template void save(Archive& ar) const { @@ -111,8 +124,15 @@ struct input_pool immer::map inners; immer::vector vectors; - friend bool operator==(const input_pool& left, - const input_pool& right) = default; + auto tie() const + { + return std::tie(bits, bits_leaf, leaves, inners, vectors); + } + + friend bool operator==(const input_pool& left, const input_pool& right) + { + return left.tie() == right.tie(); + } template void load(Archive& ar) diff --git a/immer/extra/persist/detail/type_traverse.hpp b/immer/extra/persist/detail/type_traverse.hpp index ae577a0f..8a38cbd3 100644 --- a/immer/extra/persist/detail/type_traverse.hpp +++ b/immer/extra/persist/detail/type_traverse.hpp @@ -126,13 +126,14 @@ constexpr auto insert_conditionally = [](auto map, auto pair) { } }; -inline auto get_inner_types_map_with_empty_strings(const auto& type) +template +auto get_inner_types_map_with_empty_strings(const T& type) { namespace hana = boost::hana; constexpr auto get_for_one_type = [](auto type) { - using T = typename decltype(type)::type; - return detail::get_inner_types_t::apply(); + using type_t = typename decltype(type)::type; + return detail::get_inner_types_t::apply(); }; constexpr auto get_for_many = [get_for_one_type](const auto& map) { @@ -159,7 +160,8 @@ inline auto get_inner_types_map_with_empty_strings(const auto& type) * Generate a map (type, member_name) for all members of a given type, * recursively. */ -inline auto get_inner_types_map(const auto& type) +template +auto get_inner_types_map(const T& type) { namespace hana = boost::hana; @@ -175,7 +177,8 @@ inline auto get_inner_types_map(const auto& type) return hana::to_map(result); } -inline auto get_inner_types(const auto& type) +template +auto get_inner_types(const T& type) { return boost::hana::keys( detail::get_inner_types_map_with_empty_strings(type)); diff --git a/immer/extra/persist/transform.hpp b/immer/extra/persist/transform.hpp index 8be87c2d..32852b92 100644 --- a/immer/extra/persist/transform.hpp +++ b/immer/extra/persist/transform.hpp @@ -16,7 +16,7 @@ namespace immer::persist { * @ingroup persist-transform * @see convert_container */ -template Policy = hana_struct_auto_policy> +template auto get_output_pools(const T& value0, const Policy& policy = Policy{}) { const auto types = boost::hana::to_set(policy.get_pool_types(value0)); diff --git a/justfile b/justfile index 52adf022..067fa103 100644 --- a/justfile +++ b/justfile @@ -10,7 +10,7 @@ build-valgrind-path := "build-valgrind-" + os() + "-" + arch() # Create a build directory for a Debug build without ASAN, so that valgrind can work mk-build-valgrind: (_mk-dir build-valgrind-path) - cd {{ build-valgrind-path }} ; cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Debug -Dimmer_BUILD_TESTS=ON -Dimmer_BUILD_PERSIST_TESTS=ON -Dimmer_BUILD_EXAMPLES=OFF -DCXX_STANDARD=20 + cd {{ build-valgrind-path }} ; cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Debug -Dimmer_BUILD_TESTS=ON -Dimmer_BUILD_PERSIST_TESTS=ON -Dimmer_BUILD_EXAMPLES=OFF -DCXX_STANDARD=17 [linux] run-valgrind: @@ -26,7 +26,7 @@ build-asan-path := "build-asan-" + os() + "-" + arch() # Create a build directory for a Debug build with ASAN enabled mk-build-asan: (_mk-dir build-asan-path) - cd {{ build-asan-path }} ; cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Debug -DENABLE_ASAN=ON -Dimmer_BUILD_TESTS=ON -Dimmer_BUILD_PERSIST_TESTS=ON -Dimmer_BUILD_EXAMPLES=OFF -DCXX_STANDARD=20 + cd {{ build-asan-path }} ; cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Debug -DENABLE_ASAN=ON -Dimmer_BUILD_TESTS=ON -Dimmer_BUILD_PERSIST_TESTS=ON -Dimmer_BUILD_EXAMPLES=OFF -DCXX_STANDARD=17 run-tests-asan: cd {{ build-asan-path }} ; ninja tests && ninja test diff --git a/test/extra/persist/CMakeLists.txt b/test/extra/persist/CMakeLists.txt index 213e3372..2e186ec5 100644 --- a/test/extra/persist/CMakeLists.txt +++ b/test/extra/persist/CMakeLists.txt @@ -2,8 +2,8 @@ find_package(fmt REQUIRED) find_package(cereal REQUIRED) find_package(xxHash 0.8 CONFIG REQUIRED) -if(${CXX_STANDARD} LESS 20) - message(FATAL_ERROR "persist requires C++20") +if(${CXX_STANDARD} LESS 17) + message(FATAL_ERROR "persist requires C++17") endif() include(CTest) @@ -33,7 +33,8 @@ target_link_libraries(persist-tests PRIVATE fmt::fmt Catch2::Catch2WithMain target_compile_options(persist-tests PRIVATE -O1 -fno-optimize-sibling-calls -g -fno-omit-frame-pointer) -target_compile_options(persist-tests PRIVATE -Wno-unused-function) +target_compile_options(persist-tests PRIVATE -Wno-unused-function + -Wno-c++20-designator) if(ENABLE_ASAN) target_compile_options( diff --git a/test/extra/persist/test_array.cpp b/test/extra/persist/test_array.cpp index 717b97df..6642edb9 100644 --- a/test/extra/persist/test_array.cpp +++ b/test/extra/persist/test_array.cpp @@ -137,7 +137,8 @@ namespace { template struct direct_container_policy : immer::persist::value0_serialize_t { - auto get_pool_types(const auto&) const + template + auto get_pool_types(const T&) const { return boost::hana::tuple_t; } diff --git a/test/extra/persist/test_champ.cpp b/test/extra/persist/test_champ.cpp index f874132b..511dfcdb 100644 --- a/test/extra/persist/test_champ.cpp +++ b/test/extra/persist/test_champ.cpp @@ -28,7 +28,8 @@ const auto gen_map = [](auto map, int count) { return map; }; -auto gen_table(auto table, std::size_t from, std::size_t to) +template +auto gen_table(T table, std::size_t from, std::size_t to) { for (auto i = std::min(from, to); i < std::max(from, to); ++i) { table = std::move(table).insert(test_value{i, fmt::format("_{}_", i)}); diff --git a/test/extra/persist/test_circular_dependency_conversion.cpp b/test/extra/persist/test_circular_dependency_conversion.cpp index 391b0b09..869f1550 100644 --- a/test/extra/persist/test_circular_dependency_conversion.cpp +++ b/test/extra/persist/test_circular_dependency_conversion.cpp @@ -17,6 +17,10 @@ { \ return members(left) == members(right); \ } \ + bool operator!=(const name& left, const name& right) \ + { \ + return members(left) != members(right); \ + } \ template \ void serialize(Archive& ar, name& m) \ { \ @@ -701,10 +705,13 @@ TEST_CASE("Test circular dependency pools", "[conversion]") SECTION("XML also works") { - const auto xml_str = cereal_save_with_pools( - value, immer::persist::hana_struct_auto_member_name_policy(value)); + const auto xml_str = + immer::persist::cereal_save_with_pools( + value, + immer::persist::hana_struct_auto_member_name_policy(value)); const auto loaded_value = - cereal_load_with_pools( + immer::persist::cereal_load_with_pools( xml_str, immer::persist::hana_struct_auto_member_name_policy( model::value_one{})); diff --git a/test/extra/persist/test_for_docs.cpp b/test/extra/persist/test_for_docs.cpp index 9d89882d..f423db52 100644 --- a/test/extra/persist/test_for_docs.cpp +++ b/test/extra/persist/test_for_docs.cpp @@ -20,7 +20,12 @@ struct document vector_one ints; vector_one ints2; - friend bool operator==(const document&, const document&) = default; + auto tie() const { return std::tie(ints, ints2); } + + friend bool operator==(const document& left, const document& right) + { + return left.tie() == right.tie(); + } // Make the struct serializable with cereal as usual, nothing special // related to immer::persist. @@ -147,7 +152,10 @@ struct extra_data { vector_str comments; - friend bool operator==(const extra_data&, const extra_data&) = default; + friend bool operator==(const extra_data& left, const extra_data& right) + { + return left.comments == right.comments; + } template void serialize(Archive& ar) @@ -163,7 +171,12 @@ struct doc_2 vector_str strings; extra_data extra; - friend bool operator==(const doc_2&, const doc_2&) = default; + auto tie() const { return std::tie(ints, ints2, strings, extra); } + + friend bool operator==(const doc_2& left, const doc_2& right) + { + return left.tie() == right.tie(); + } template void serialize(Archive& ar) @@ -179,7 +192,8 @@ struct doc_2 // include:start-doc_2_policy struct doc_2_policy { - auto get_pool_types(const auto&) const + template + auto get_pool_types(const T&) const { return boost::hana::tuple_t; } @@ -261,7 +275,12 @@ struct document_str vector_str str; vector_str str2; - friend bool operator==(const document_str&, const document_str&) = default; + auto tie() const { return std::tie(str, str2); } + + friend bool operator==(const document_str& left, const document_str& right) + { + return left.tie() == right.tie(); + } template void serialize(Archive& ar) @@ -399,7 +418,8 @@ namespace { template struct direct_container_policy : immer::persist::value0_serialize_t { - auto get_pool_types(const auto&) const + template + auto get_pool_types(const T&) const { return boost::hana::tuple_t; } @@ -465,7 +485,10 @@ struct new_id_t { std::string id; - friend bool operator==(const new_id_t&, const new_id_t&) = default; + friend bool operator==(const new_id_t& left, const new_id_t& right) + { + return left.id == right.id; + } friend std::size_t xx_hash_value(const new_id_t& value) { @@ -478,7 +501,12 @@ struct new_item new_id_t id; std::string data; - friend bool operator==(const new_item&, const new_item&) = default; + auto tie() const { return std::tie(id, data); } + + friend bool operator==(const new_item& left, const new_item& right) + { + return left.tie() == right.tie(); + } }; // include:end-new-table-types } // namespace @@ -603,7 +631,10 @@ struct nested_t { vector_one ints; - friend bool operator==(const nested_t&, const nested_t&) = default; + friend bool operator==(const nested_t& left, const nested_t& right) + { + return left.ints == right.ints; + } template void serialize(Archive& ar) @@ -616,8 +647,11 @@ struct with_nested_t { immer::vector nested; - friend bool operator==(const with_nested_t&, - const with_nested_t&) = default; + friend bool operator==(const with_nested_t& left, + const with_nested_t& right) + { + return left.nested == right.nested; + } template void serialize(Archive& ar) @@ -631,7 +665,10 @@ struct new_nested_t { vector_str str; - friend bool operator==(const new_nested_t&, const new_nested_t&) = default; + friend bool operator==(const new_nested_t& left, const new_nested_t& right) + { + return left.str == right.str; + } template void serialize(Archive& ar) @@ -644,8 +681,11 @@ struct with_new_nested_t { immer::vector nested; - friend bool operator==(const with_new_nested_t&, - const with_new_nested_t&) = default; + friend bool operator==(const with_new_nested_t& left, + const with_new_nested_t& right) + { + return left.nested == right.nested; + } template void serialize(Archive& ar) diff --git a/test/extra/persist/test_special_pool_auto.cpp b/test/extra/persist/test_special_pool_auto.cpp index e911f7d0..ee592d33 100644 --- a/test/extra/persist/test_special_pool_auto.cpp +++ b/test/extra/persist/test_special_pool_auto.cpp @@ -177,8 +177,9 @@ TEST_CASE("Auto-persisting") // REQUIRE(json_str == ""); { - const auto loaded = from_json_with_auto_pool( - json_str, pool_types); + const auto loaded = + test::from_json_with_auto_pool(json_str, + pool_types); REQUIRE(loaded == value); } } @@ -741,6 +742,14 @@ TEST_CASE("Test table with a funny value no auto") const auto json_str = immer::persist::cereal_save_with_pools( value, immer::persist::via_get_pools_names_policy(value)); + + SECTION("Test streaming") + { + auto os = std::ostringstream{}; + immer::persist::cereal_save_with_pools( + os, value, immer::persist::via_get_pools_names_policy(value)); + REQUIRE(os.str() == json_str); + } // REQUIRE(json_str == ""); const auto loaded = @@ -944,18 +953,17 @@ TEST_CASE("Test types traversal") test_variant::with_variant>(); SECTION("It goes inside variant") { - using contains_t = decltype(names[hana::type_c>] == - BOOST_HANA_STRING("ints")); - static_assert(contains_t::value); + auto contains = names[hana::type_c>] == + BOOST_HANA_STRING("ints"); + static_assert(decltype(contains)::value); } SECTION("Check types with names") { - using contains_map_t = - decltype(names[hana::type_c< - immer::map>>] == - BOOST_HANA_STRING("int_map")); - static_assert(contains_map_t::value); + auto contains_map = + names[hana::type_c>>] == + BOOST_HANA_STRING("int_map"); + static_assert(decltype(contains_map)::value); // It doesn't find the vector because there is no name for it const auto vector_type = hana::type_c>; diff --git a/test/extra/persist/test_vectors.cpp b/test/extra/persist/test_vectors.cpp index 3c8404ff..4f52bbf7 100644 --- a/test/extra/persist/test_vectors.cpp +++ b/test/extra/persist/test_vectors.cpp @@ -28,7 +28,8 @@ using immer::persist::rbts::add_to_pool; using json_t = nlohmann::json; namespace hana = boost::hana; -auto load_vec(const auto& json, std::size_t vec_id) +template +auto load_vec(const T& json, std::size_t vec_id) { const auto pool = test::from_json>(json); @@ -37,7 +38,8 @@ auto load_vec(const auto& json, std::size_t vec_id) return loader.load(container_id{vec_id}); } -auto load_flex_vec(const auto& json, std::size_t vec_id) +template +auto load_flex_vec(const T& json, std::size_t vec_id) { const auto pool = test::from_json>(json); @@ -587,11 +589,15 @@ template class show_type; template -using node_for = typename decltype([] { +auto get_node_for() +{ using rbtree_t = std::decay_t{}.impl())>; using node_t = typename rbtree_t::node_t; return boost::hana::type_c; -}())::type; +} + +template +using node_for = typename decltype(get_node_for())::type; } // namespace TEST_CASE("Test vector with very big objects") diff --git a/test/extra/persist/utils.hpp b/test/extra/persist/utils.hpp index c3ca58be..2ba5d549 100644 --- a/test/extra/persist/utils.hpp +++ b/test/extra/persist/utils.hpp @@ -33,7 +33,8 @@ using example_output_pool = using example_loader = immer::persist::rbts:: loader; -inline auto gen(auto init, int count) +template +auto gen(T init, int count) { for (int i = 0; i < count; ++i) { init = std::move(init).push_back(i); @@ -158,7 +159,8 @@ constexpr auto convert_old_type_table = boost::hana::overload( }, convert_old_type); -inline auto transform_vec(const auto& vec) +template +auto transform_vec(const T& vec) { auto result = vector_one{}; for (const auto& item : vec) { @@ -167,7 +169,8 @@ inline auto transform_vec(const auto& vec) return result; } -inline auto transform_map(const auto& map) +template +auto transform_map(const T& map) { auto result = immer:: map>{}; @@ -177,7 +180,8 @@ inline auto transform_map(const auto& map) return result; } -inline auto transform_table(const auto& table) +template +auto transform_table(const T& table) { auto result = immer::table Date: Fri, 13 Sep 2024 16:58:03 +0200 Subject: [PATCH 2/2] cereal loading into containers must not append Fix and test the loading and saving behavior for all immer containers --- immer/extra/cereal/immer_array.hpp | 1 + .../extra}/cereal/immer_box.hpp | 0 immer/extra/cereal/immer_map.hpp | 2 + .../extra}/cereal/immer_set.hpp | 1 + .../extra}/cereal/immer_table.hpp | 1 + immer/extra/cereal/immer_vector.hpp | 2 + .../persist/detail/cereal/compact_map.hpp | 1 + test/extra/persist/CMakeLists.txt | 1 + .../test_circular_dependency_conversion.cpp | 6 +- test/extra/persist/test_containers_cereal.cpp | 217 ++++++++++++++++++ 10 files changed, 229 insertions(+), 3 deletions(-) rename {test/extra/persist => immer/extra}/cereal/immer_box.hpp (100%) rename {test/extra/persist => immer/extra}/cereal/immer_set.hpp (98%) rename {test/extra/persist => immer/extra}/cereal/immer_table.hpp (98%) create mode 100644 test/extra/persist/test_containers_cereal.cpp diff --git a/immer/extra/cereal/immer_array.hpp b/immer/extra/cereal/immer_array.hpp index 786da0c3..9d705ff5 100644 --- a/immer/extra/cereal/immer_array.hpp +++ b/immer/extra/cereal/immer_array.hpp @@ -10,6 +10,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::array& m) { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; diff --git a/test/extra/persist/cereal/immer_box.hpp b/immer/extra/cereal/immer_box.hpp similarity index 100% rename from test/extra/persist/cereal/immer_box.hpp rename to immer/extra/cereal/immer_box.hpp diff --git a/immer/extra/cereal/immer_map.hpp b/immer/extra/cereal/immer_map.hpp index 88cbc3fa..3acf1c8a 100644 --- a/immer/extra/cereal/immer_map.hpp +++ b/immer/extra/cereal/immer_map.hpp @@ -33,6 +33,7 @@ CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::map& m) { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; @@ -71,6 +72,7 @@ CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::map& m) { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { K k; diff --git a/test/extra/persist/cereal/immer_set.hpp b/immer/extra/cereal/immer_set.hpp similarity index 98% rename from test/extra/persist/cereal/immer_set.hpp rename to immer/extra/cereal/immer_set.hpp index 75e7f5f3..d953cd69 100644 --- a/test/extra/persist/cereal/immer_set.hpp +++ b/immer/extra/cereal/immer_set.hpp @@ -16,6 +16,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::set& m) { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; diff --git a/test/extra/persist/cereal/immer_table.hpp b/immer/extra/cereal/immer_table.hpp similarity index 98% rename from test/extra/persist/cereal/immer_table.hpp rename to immer/extra/cereal/immer_table.hpp index 64320850..93fd3e3d 100644 --- a/test/extra/persist/cereal/immer_table.hpp +++ b/immer/extra/cereal/immer_table.hpp @@ -17,6 +17,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::table& m) { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; diff --git a/immer/extra/cereal/immer_vector.hpp b/immer/extra/cereal/immer_vector.hpp index 93bef240..9d902feb 100644 --- a/immer/extra/cereal/immer_vector.hpp +++ b/immer/extra/cereal/immer_vector.hpp @@ -15,6 +15,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; @@ -46,6 +47,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; diff --git a/immer/extra/persist/detail/cereal/compact_map.hpp b/immer/extra/persist/detail/cereal/compact_map.hpp index 6a63d4a6..b118a604 100644 --- a/immer/extra/persist/detail/cereal/compact_map.hpp +++ b/immer/extra/persist/detail/cereal/compact_map.hpp @@ -58,6 +58,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, { size_type size; ar(make_size_tag(size)); + m.map = {}; for (auto i = size_type{}; i < size; ++i) { auto pair = immer::persist::detail::compact_pair{}; diff --git a/test/extra/persist/CMakeLists.txt b/test/extra/persist/CMakeLists.txt index 2e186ec5..d2986db3 100644 --- a/test/extra/persist/CMakeLists.txt +++ b/test/extra/persist/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable( test_circular_dependency_conversion.cpp test_table_box_recursive.cpp test_for_docs.cpp + test_containers_cereal.cpp ${PROJECT_SOURCE_DIR}/immer/extra/persist/xxhash/xxhash_64.cpp) target_precompile_headers( persist-tests PRIVATE diff --git a/test/extra/persist/test_circular_dependency_conversion.cpp b/test/extra/persist/test_circular_dependency_conversion.cpp index 869f1550..09f085e1 100644 --- a/test/extra/persist/test_circular_dependency_conversion.cpp +++ b/test/extra/persist/test_circular_dependency_conversion.cpp @@ -5,9 +5,9 @@ #include "utils.hpp" -#include -#include -#include +#include +#include +#include #include #include diff --git a/test/extra/persist/test_containers_cereal.cpp b/test/extra/persist/test_containers_cereal.cpp new file mode 100644 index 00000000..66962fef --- /dev/null +++ b/test/extra/persist/test_containers_cereal.cpp @@ -0,0 +1,217 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace { + +using json_t = nlohmann::json; + +struct with_id +{ + int id = 99; + std::string data = "_data_"; + + auto tie() const { return std::tie(id, data); } + + template + void serialize(Archive& ar) + { + ar(CEREAL_NVP(id), CEREAL_NVP(data)); + } + + friend bool operator==(const with_id& left, const with_id& right) + { + return left.tie() == right.tie(); + } + + friend std::ostream& operator<<(std::ostream& s, const with_id& value) + { + auto ar = cereal::JSONOutputArchive{s}; + ar(value); + return s; + } +}; + +// A struct with non-empty default constructed containers +struct non_empty_default +{ + immer::vector vec = {1, 2, 3}; + immer::flex_vector flex_vec = {4, 5, 6}; + immer::array arr = {7, 8, 9}; + immer::map map = { + {1, "one"}, + {2, "two"}, + }; + immer::map with_auto_id = { + {1, + with_id{ + .id = 1, + .data = "oner", + }}, + {2, + with_id{ + .id = 2, + .data = "twoer", + }}, + {3, + with_id{ + .id = 3, + .data = "threer", + }}, + }; + immer::map compact_map = { + {"one", 1}, + {"two", 2}, + }; + immer::box box{"default!"}; + immer::set set = {6, 7, 8, 9}; + immer::table table = { + with_id{ + .id = 1, + .data = "oner", + }, + with_id{ + .id = 2, + .data = "twoer", + }, + with_id{ + .id = 3, + .data = "threer", + }, + }; + + auto tie() const + { + return std::tie(vec, + flex_vec, + arr, + map, + with_auto_id, + compact_map, + box, + set, + table); + } + + template + void serialize(Archive& ar) + { + ar(CEREAL_NVP(vec), + CEREAL_NVP(flex_vec), + CEREAL_NVP(arr), + CEREAL_NVP(map), + CEREAL_NVP(with_auto_id), + cereal::make_nvp( + "compact_map", + immer::persist::detail::make_compact_map(compact_map)), + CEREAL_NVP(box), + CEREAL_NVP(set), + CEREAL_NVP(table)); + } + + friend bool operator==(const non_empty_default& left, + const non_empty_default& right) + { + return left.tie() == right.tie(); + } + + friend std::ostream& operator<<(std::ostream& s, + const non_empty_default& value) + { + auto ar = cereal::JSONOutputArchive{s}; + ar(value); + return s; + } +}; + +} // namespace + +TEST_CASE("Test loading struct with non-empty containers") +{ + // Create some non-default data + const auto value = non_empty_default{ + .vec = {4, 5}, + .flex_vec = {6, 7}, + .arr = {1, 2}, + .map = + { + {3, "three"}, + {4, "four"}, + }, + .with_auto_id = + { + {5, + with_id{ + .id = 5, + .data = "fiver", + }}, + {6, + with_id{ + .id = 6, + .data = "sixer", + }}, + }, + .compact_map = + { + {"three", 3}, + {"four", 4}, + }, + .box = "non-default-box", + .set = {1, 3, 5}, + .table = + { + with_id{ + .id = 5, + .data = "fiver", + }, + with_id{ + .id = 6, + .data = "sixer", + }, + }, + }; + const auto str = [&] { + auto os = std::ostringstream{}; + { + auto ar = cereal::JSONOutputArchive{os}; + ar(value); + } + return os.str(); + }(); + + const auto expected_json = json_t::parse(R"( +{ + "value0": { + "arr": [1, 2], + "box": {"value0": "non-default-box"}, + "compact_map": [["three", 3], ["four", 4]], + "flex_vec": [6, 7], + "map": [{"key": 3, "value": "three"}, {"key": 4, "value": "four"}], + "set": [1, 3, 5], + "table": [{"data": "fiver", "id": 5}, {"data": "sixer", "id": 6}], + "vec": [4, 5], + "with_auto_id": [{"data": "fiver", "id": 5}, {"data": "sixer", "id": 6}] + } +} + )"); + REQUIRE(json_t::parse(str) == expected_json); + + const auto loaded_value = [&] { + auto is = std::istringstream{str}; + auto ar = cereal::JSONInputArchive{is}; + auto r = non_empty_default{}; + ar(r); + return r; + }(); + REQUIRE(value == loaded_value); +}