diff --git a/include/moose/archive.i b/include/moose/archive.i index bdd4415..894d9dc 100644 --- a/include/moose/archive.i +++ b/include/moose/archive.i @@ -205,21 +205,57 @@ namespace moose template void Archive::archive (const char* name, T& value, EntryTypeDummy ) { + using Traits = TypeTraits; + using ValueType = typename Traits::ValueType; + + constexpr bool unpack = wantsToUnpack () && canBeUnpacked (); + if (is_reading ()) { - TypeTraits::clear (value); - while(mInput->array_has_next (name)) + Traits::clear (value); + if constexpr (unpack) + { + while(mInput->array_has_next (name)) + { + ValueType childValue; + auto childRange = make_range (childValue); + for (auto i = childRange.begin; i != childRange.end; ++i) + { + if (!mInput->array_has_next (name)) + throw ArchiveError () << "Too few entries while reading range '" << name << "'"; + + (*this) ("", *i); + } + Traits::pushBack (value, childValue); + } + } + else { - using ValueType = typename TypeTraits::ValueType; - ValueType tmpVal = detail::GetInitialValue (); - (*this) ("", tmpVal); - TypeTraits::pushBack (value, tmpVal); + while(mInput->array_has_next (name)) + { + ValueType tmpVal = detail::GetInitialValue (); + (*this) ("", tmpVal); + Traits::pushBack (value, tmpVal); + } } } else { - // Writing a vector type is the same as writing a range - archive (name, value, EntryTypeDummy ()); + if constexpr (unpack) + { + auto range = make_range (value); + for (auto i = range.begin; i != range.end; ++i) + { + auto childRange = make_range (*i); + for (auto j = childRange.begin; j != childRange.end; ++j) + (*this) ("", *j); + } + } + else + { + // Writing a vector type is the same as writing a range + archive (name, value, EntryTypeDummy ()); + } } } diff --git a/include/moose/stl_serialization.h b/include/moose/stl_serialization.h index e9d3d64..ea5bc32 100644 --- a/include/moose/stl_serialization.h +++ b/include/moose/stl_serialization.h @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -55,6 +54,8 @@ namespace moose static constexpr EntryType entryType = EntryType::Vector; + static constexpr bool wantsToUnpack = true; + static void pushBack (Type& vector, ValueType const& value) { vector.push_back (value); } diff --git a/include/moose/type_traits.h b/include/moose/type_traits.h index fec9528..2bee343 100644 --- a/include/moose/type_traits.h +++ b/include/moose/type_traits.h @@ -37,8 +37,24 @@ namespace moose enum class EntryType { Struct, - Value, ///< Built in values. Todo: Call this `BuiltIn` + + Value, ///< Plain values. Those are directly supported by the archive. + + /** A fixed size range of entries, featuring a `begin` and `end` accessor. + If no `begin` or `end` accessors are available, one may overload the `make_range` function + to create some pair of iterators. + If a range consists of `Values`, then the contents of that `range` can be unpacked into + its parent `vector`, if the traits specify the following constant: + \code + template <> + struct TypeTraits + { + static constexpr bool canBeUnpacked = true; + }; + \endcode + */ Range, + /** A dynamic sequence of values. Think of a `std::vector`. The following typetraits has to be specified for it to be compatible with the archive. \code @@ -52,8 +68,19 @@ namespace moose static void clear (Type& vector); }; \endcode + + Child ranges may be unpacked by a vector if the following constant is additionally specified: + \code + template <> + struct TypeTraits + { + ... + static constexpr bool wantsToUnpack = true; + }; + \endcode */ Vector, + /** Types which just wrap a single value of a different type may not need a custom entry. Instead it may be convenient to just forward the wrapped value for serialization. For such type, the TypeTraits have to specify the following: @@ -69,6 +96,7 @@ namespace moose \endcode */ ForwardValue, + /** Just like `ForwardValue`, but `getForwardedValue` returns a reference.*/ ForwardReference, }; @@ -83,6 +111,62 @@ namespace moose static constexpr EntryType entryType = EntryType::Struct; }; + template + constexpr bool isValue () + { return TypeTraits::entryType == EntryType::Value; } + + template + constexpr bool isStruct () + { return TypeTraits::entryType == EntryType::Struct; } + + template + constexpr bool isRange () + { return TypeTraits::entryType == EntryType::Range; } + + template + constexpr bool isVector () + { return TypeTraits::entryType == EntryType::Vector; } + + template + constexpr bool isForwardValue () + { return TypeTraits::entryType == EntryType::ForwardValue; } + + template + constexpr bool isForwardReference () + { return TypeTraits::entryType == EntryType::ForwardReference; } + + template + concept TraitsHas_canBeUnpacked = requires () + { {TypeTraits::canBeUnpacked} -> std::convertible_to; }; + + template + constexpr bool canBeUnpacked () + { + return isRange () && TypeTraits::canBeUnpacked; + } + + template + constexpr bool canBeUnpacked () + { + return false; + } + + template + concept TraitsHas_wantsToUnpack = requires () + { {TypeTraits::wantsToUnpack} -> std::convertible_to; }; + + template + constexpr bool wantsToUnpack () + { + return isVector () && TypeTraits::wantsToUnpack; + } + + template + constexpr bool wantsToUnpack () + { + return false; + } + /** Overload this method for your types to specify a custom default hint which will be used during serialization, if a user didn't specify a hint in the archive call. */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8a8e28a..775b854 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable ( enums.t.cpp json_archive_in.t.cpp stl.t.cpp + unpacking.t.cpp version.t.cpp) target_compile_features(moose_tests PUBLIC cxx_std_20) diff --git a/tests/stl.t.cpp b/tests/stl.t.cpp index 66b290e..720f105 100644 --- a/tests/stl.t.cpp +++ b/tests/stl.t.cpp @@ -67,7 +67,7 @@ TEST (stl, variant) EXPECT_EQ (v3, toBinaryAndBack (v3)); } -TEST (stl, nestedInlineArrays) +TEST (stl, nestedArrays) { auto const jsonArrays = R"""({ "pairs": [[0, 1], [2, 3], [4, 5]] })"""; auto const arrays = fromJson>> ("pairs", jsonArrays); diff --git a/tests/unpacking.t.cpp b/tests/unpacking.t.cpp new file mode 100644 index 0000000..2804942 --- /dev/null +++ b/tests/unpacking.t.cpp @@ -0,0 +1,54 @@ +#include + +#include "utils.h" + +#include + +using namespace moose; + +template +struct UnpackMe +{ + auto begin () {return mData.begin ();} + auto end () {return mData.end ();} + auto operator <=> (UnpackMe const& other) const = default; + + std::array mData; +}; + +template +struct TypeTraits> +{ + static constexpr EntryType entryType = EntryType::Range; + static constexpr bool canBeUnpacked = true; +}; + +template +auto getDefaultHint (UnpackMe const&) -> Hint +{ + return Hint::OneLine; +} + +template +auto getDefaultHint (std::vector> const&) -> Hint +{ + return Hint::OneLine; +} + +TEST (stl, unpackData) +{ + using Data = UnpackMe; + EXPECT_EQ (canBeUnpacked (), true); + auto const json = R"""({ "pairs": [0, 1, 2, 3, 4, 5] })"""; + auto const jsonArrays = fromJson> ("pairs", json); + auto const expectedArrays = std::vector {{0, 1}, {2, 3}, {4, 5}}; + EXPECT_EQ (jsonArrays, expectedArrays); + + auto const json2 = toJson ("pairs", expectedArrays); + auto const jsonArrays2 = fromJson> ("pairs", json); + EXPECT_EQ (jsonArrays2, expectedArrays); + + auto const binary = toBinary (expectedArrays); + auto const binaryArrays = fromBinary> (binary); + EXPECT_EQ (binaryArrays, expectedArrays); +}