Skip to content

Commit

Permalink
Unpacking of ranges is now supported from inside vectors.
Browse files Browse the repository at this point in the history
  • Loading branch information
sreiter committed Jul 5, 2024
1 parent eb83ce5 commit e7292cf
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 11 deletions.
52 changes: 44 additions & 8 deletions include/moose/archive.i
Original file line number Diff line number Diff line change
Expand Up @@ -205,21 +205,57 @@ namespace moose
template <class T>
void Archive::archive (const char* name, T& value, EntryTypeDummy <EntryType::Vector>)
{
using Traits = TypeTraits<T>;
using ValueType = typename Traits::ValueType;

constexpr bool unpack = wantsToUnpack<T> () && canBeUnpacked<ValueType> ();

if (is_reading ())
{
TypeTraits<T>::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<T>::ValueType;
ValueType tmpVal = detail::GetInitialValue <ValueType> ();
(*this) ("", tmpVal);
TypeTraits<T>::pushBack (value, tmpVal);
while(mInput->array_has_next (name))
{
ValueType tmpVal = detail::GetInitialValue <ValueType> ();
(*this) ("", tmpVal);
Traits::pushBack (value, tmpVal);
}
}
}
else
{
// Writing a vector type is the same as writing a range
archive (name, value, EntryTypeDummy <EntryType::Range> ());
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 <EntryType::Range> ());
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion include/moose/stl_serialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
#include <moose/stl/array.h>
#include <moose/stl/optional.h>
#include <moose/stl/variant.h>
#include <array>
#include <map>
#include <set>
#include <utility>
Expand All @@ -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); }

Expand Down
86 changes: 85 additions & 1 deletion include/moose/type_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<YourChildRange>
{
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
Expand All @@ -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<YourParentRange>
{
...
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:
Expand All @@ -69,6 +96,7 @@ namespace moose
\endcode
*/
ForwardValue,

/** Just like `ForwardValue`, but `getForwardedValue` returns a reference.*/
ForwardReference,
};
Expand All @@ -83,6 +111,62 @@ namespace moose
static constexpr EntryType entryType = EntryType::Struct;
};

template <class T>
constexpr bool isValue ()
{ return TypeTraits<T>::entryType == EntryType::Value; }

template <class T>
constexpr bool isStruct ()
{ return TypeTraits<T>::entryType == EntryType::Struct; }

template <class T>
constexpr bool isRange ()
{ return TypeTraits<T>::entryType == EntryType::Range; }

template <class T>
constexpr bool isVector ()
{ return TypeTraits<T>::entryType == EntryType::Vector; }

template <class T>
constexpr bool isForwardValue ()
{ return TypeTraits<T>::entryType == EntryType::ForwardValue; }

template <class T>
constexpr bool isForwardReference ()
{ return TypeTraits<T>::entryType == EntryType::ForwardReference; }

template <class T>
concept TraitsHas_canBeUnpacked = requires ()
{ {TypeTraits<T>::canBeUnpacked} -> std::convertible_to<bool>; };

template <TraitsHas_canBeUnpacked T>
constexpr bool canBeUnpacked ()
{
return isRange<T> () && TypeTraits<T>::canBeUnpacked;
}

template <class T>
constexpr bool canBeUnpacked ()
{
return false;
}

template <class T>
concept TraitsHas_wantsToUnpack = requires ()
{ {TypeTraits<T>::wantsToUnpack} -> std::convertible_to<bool>; };

template <TraitsHas_wantsToUnpack T>
constexpr bool wantsToUnpack ()
{
return isVector<T> () && TypeTraits<T>::wantsToUnpack;
}

template <class T>
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.
*/
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/stl.t.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::vector<std::array<int, 2>>> ("pairs", jsonArrays);
Expand Down
54 changes: 54 additions & 0 deletions tests/unpacking.t.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include <moose/stl_serialization.h>

#include "utils.h"

#include <gtest/gtest.h>

using namespace moose;

template <class T>
struct UnpackMe
{
auto begin () {return mData.begin ();}
auto end () {return mData.end ();}
auto operator <=> (UnpackMe const& other) const = default;

std::array<T, 2> mData;
};

template <class T>
struct TypeTraits<UnpackMe<T>>
{
static constexpr EntryType entryType = EntryType::Range;
static constexpr bool canBeUnpacked = true;
};

template <class T>
auto getDefaultHint (UnpackMe<T> const&) -> Hint
{
return Hint::OneLine;
}

template <class T>
auto getDefaultHint (std::vector<UnpackMe<T>> const&) -> Hint
{
return Hint::OneLine;
}

TEST (stl, unpackData)
{
using Data = UnpackMe<int>;
EXPECT_EQ (canBeUnpacked<Data> (), true);
auto const json = R"""({ "pairs": [0, 1, 2, 3, 4, 5] })""";
auto const jsonArrays = fromJson<std::vector<Data>> ("pairs", json);
auto const expectedArrays = std::vector<Data> {{0, 1}, {2, 3}, {4, 5}};
EXPECT_EQ (jsonArrays, expectedArrays);

auto const json2 = toJson ("pairs", expectedArrays);
auto const jsonArrays2 = fromJson<std::vector<Data>> ("pairs", json);
EXPECT_EQ (jsonArrays2, expectedArrays);

auto const binary = toBinary (expectedArrays);
auto const binaryArrays = fromBinary<std::vector<Data>> (binary);
EXPECT_EQ (binaryArrays, expectedArrays);
}

0 comments on commit e7292cf

Please sign in to comment.