Skip to content

Commit

Permalink
remove dfs in struct_viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
Yan-Tong Lin committed Aug 2, 2024
1 parent ef142ee commit ad20f33
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 264 deletions.
337 changes: 75 additions & 262 deletions include/fixed_containers/struct_view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,81 @@
- Walker Specialization
*/

namespace fixed_containers::struct_view_detail
{
// TODO: make sure we modify this concept after we support unit constructor and put it under
// concepts.hpp
template <typename T>
concept ReflectionConstructible = ConstexprDefaultConstructible<T>;

template <typename T>
concept OptionalLike = requires(T t) {
typename T::value_type;
{ t.has_value() } -> std::same_as<bool>;
{ t.value() } -> std::same_as<typename T::value_type&>;
{ t.emplace() } -> std::same_as<typename T::value_type&>;
{ t.reset() } -> std::same_as<void>;
};

template <typename T>
concept DurationLike = requires {
typename T::rep;
typename T::period;
typename std::is_arithmetic<typename T::rep>::type;
{ T::min() } -> std::same_as<T>;
{ T::max() } -> std::same_as<T>;
{ T::zero() } -> std::same_as<T>;
};

template <typename T>
concept ResizableIterable = requires {
typename T::value_type;
{ T{}.size() } -> std::same_as<std::size_t>;
{ T{}.capacity() } -> std::same_as<std::size_t>;
{ T{}.resize(std::declval<std::size_t>()) } -> std::same_as<void>;
};

// type traits for StdArray
template <typename T>
struct IsStdArray : std::false_type
{
};

template <typename T, std::size_t MAXIMUM_SIZE>
struct IsStdArray<std::array<T, MAXIMUM_SIZE>> : std::true_type
{
};

template <typename T>
concept StdArray = IsStdArray<T>::value;

template <typename T>
concept Bitset = requires { requires std::same_as<T, std::bitset<T{}.size()>>; };

// details of supported primitives
template <typename T>
concept EnumValue = std::is_enum_v<T>;

template <typename T>
concept EnumView = std::is_same_v<T, std::string_view>;

// The list of supported primitives
template <typename T>
concept PrimitiveValue = std::is_arithmetic_v<T> || DurationLike<T> || Bitset<T>;

template <typename T>
concept PrimitiveView = std::is_pointer_v<T> && PrimitiveValue<std::remove_pointer_t<T>>;

template <typename T>
concept Primitive = PrimitiveValue<T> || PrimitiveView<T>;

// We want to consider contiguous sized range that are not optional or bitset as iterable in the
// scope of this library
template <typename T>
concept Iterable = (std::ranges::sized_range<T> && std::ranges::contiguous_range<T>) &&
(!OptionalLike<T> && !Bitset<T> && !std::is_same_v<T, std::string_view>);
} // namespace fixed_containers::struct_view_detail

namespace fixed_containers::struct_view_detail
{

Expand Down Expand Up @@ -203,90 +278,6 @@ class OptionalView
template <typename T, std::size_t MAXIMUM_SIZE>
using ResizableIterableView = FixedVector<T, MAXIMUM_SIZE>;

// type traits for StdArray
template <typename T>
struct IsStdArray : std::false_type
{
};

template <typename T, std::size_t MAXIMUM_SIZE>
struct IsStdArray<std::array<T, MAXIMUM_SIZE>> : std::true_type
{
};

// Recursion Strategy Concepts

// TODO: make sure we modify this concept after we support unit constructor and put it under
// concepts.hpp
template <typename T>
concept ReflectionConstructible = ConstexprDefaultConstructible<T>;

// special cases for supported private types
template <typename T>
concept OptionalLike = requires(T t) {
typename T::value_type;
{ t.has_value() } -> std::same_as<bool>;
{ t.value() } -> std::same_as<typename T::value_type&>;
{ t.emplace() } -> std::same_as<typename T::value_type&>;
{ t.reset() } -> std::same_as<void>;
};

template <typename T>
concept DurationLike = requires {
typename T::rep;
typename T::period;
typename std::is_arithmetic<typename T::rep>::type;
{ T::min() } -> std::same_as<T>;
{ T::max() } -> std::same_as<T>;
{ T::zero() } -> std::same_as<T>;
};

template <typename T>
concept ResizableIterable = requires {
typename T::value_type;
{ T{}.size() } -> std::same_as<std::size_t>;
{ T{}.capacity() } -> std::same_as<std::size_t>;
{ T{}.resize(std::declval<std::size_t>()) } -> std::same_as<void>;
};

template <typename T>
concept StdArray = IsStdArray<T>::value;

template <typename T>
concept Bitset = requires { requires std::same_as<T, std::bitset<T{}.size()>>; };

// details of supported primitives
template <typename T>
concept EnumValue = std::is_enum_v<T>;

template <typename T>
concept EnumView = std::is_same_v<T, std::string_view>;

// The list of supported primitives
template <typename T>
concept PrimitiveValue = std::is_arithmetic_v<T> || DurationLike<T> || Bitset<T>;

template <typename T>
concept PrimitiveView = std::is_pointer_v<T> && PrimitiveValue<std::remove_pointer_t<T>>;

template <typename T>
concept Primitive = PrimitiveValue<T> || PrimitiveView<T>;

// TODO: remove the need of ResizableIterable<T> as special case by implementing trait for
// ResizableIterableView
// We want to consider contiguous sized range that are not optional or bitset as iterable in the
// scope of this library
template <typename T>
concept Iterable =
((std::ranges::sized_range<T> && std::ranges::contiguous_range<T>) || ResizableIterable<T>) &&
(!OptionalLike<T> && !Bitset<T> && !std::is_same_v<T, std::string_view>);

// allow user to specify types they do not wish to reflect into by template specialization
template <typename T>
struct NoRecurse : std::false_type
{
};

// The following defines the disjoint set of how the metadata is stored and used
// TODO: should befine tree/false for provider and consumer
template <typename T>
Expand All @@ -308,161 +299,6 @@ template <typename T>
concept MetadataCovered = MetadataOptional<T> || MetadataFixedVector<T> || MetadataIterable<T> ||
MetadataEnum<T> || MetadataPrimitive<T>;

// The following defines the disjoint set of how we recurse
template <typename T>
concept StrategyIterable = Iterable<T> && ReflectionConstructible<T> && !NoRecurse<T>::value;

template <typename T>
concept StrategyOptional = OptionalLike<T> && ReflectionConstructible<T> && !NoRecurse<T>::value;

template <typename T>
concept StrategyPrimitive = (Primitive<T> || EnumValue<T> || EnumView<T>) && !NoRecurse<T>::value;

template <typename T>
concept StrategyReflect =
reflection::Reflectable<T> &&
!(StrategyIterable<T> || StrategyOptional<T> || StrategyPrimitive<T>) && !NoRecurse<T>::value;

template <typename T>
concept StrategyCovered =
StrategyIterable<T> || StrategyOptional<T> || StrategyPrimitive<T> || StrategyReflect<T>;

template <typename S, typename PreFunction, typename PostFunction>
constexpr void for_each_path_dfs_helper(S&& instance,
PreFunction&& pre_fn,
PostFunction&& post_fn,
fixed_containers::in_out<PathNameChain> chain);

template <typename S, typename PreFunction, typename PostFunction>
requires(StrategyOptional<std::decay_t<S>>)
constexpr void for_each_path_dfs_helper(S&& instance,
PreFunction&& pre_fn,
PostFunction&& post_fn,
fixed_containers::in_out<PathNameChain> chain);
template <typename S, typename PreFunction, typename PostFunction>
requires(StrategyIterable<std::decay_t<S>>)
constexpr void for_each_path_dfs_helper(S&& instance,
PreFunction&& pre_fn,
PostFunction&& post_fn,
fixed_containers::in_out<PathNameChain> chain);
template <typename S, typename PreFunction, typename PostFunction>
requires(StrategyReflect<std::decay_t<S>>)
constexpr void for_each_path_dfs_helper(S&& instance,
PreFunction&& pre_fn,
PostFunction&& post_fn,
fixed_containers::in_out<PathNameChain> chain);
template <typename S, typename PreFunction, typename PostFunction>
requires(StrategyPrimitive<std::decay_t<S>>)
constexpr void for_each_path_dfs_helper(S&& instance,
PreFunction&& pre_fn,
PostFunction&& post_fn,
fixed_containers::in_out<PathNameChain> chain);

template <typename S, typename PreFunction, typename PostFunction>
requires(StrategyOptional<std::decay_t<S>>)
constexpr void for_each_path_dfs_helper(S&& instance,
PreFunction&& pre_fn,
PostFunction&& post_fn,
fixed_containers::in_out<PathNameChain> chain)
{
std::forward<PreFunction>(pre_fn)(std::as_const(*chain), std::forward<S>(instance));
// recurse into `value()` if exists, construct a dummy instance at `value()` if not exists
// unsafe cast to non-const
// However, we only construct when the optional has no value and destruct at the end of
// recursion
chain->push_back(OPTIONAL_PATH_NAME);
using InstanceType = decltype(instance);
bool constructed{false};
if (!instance.has_value())
{
const_cast<std::decay_t<InstanceType>*>(std::addressof(instance))->emplace();
constructed = true;
}
assert_or_abort(instance.has_value());
for_each_path_dfs_helper(instance.value(),
std::forward<PreFunction>(pre_fn),
std::forward<PostFunction>(post_fn),
fixed_containers::in_out{*chain});
if (constructed)
{
const_cast<std::decay_t<InstanceType>*>(std::addressof(instance))->reset();
}
chain->pop_back();
std::forward<PostFunction>(post_fn)(std::as_const(*chain), std::forward<S>(instance));
}

template <typename S, typename PreFunction, typename PostFunction>
requires(StrategyIterable<std::decay_t<S>>)
constexpr void for_each_path_dfs_helper(S&& instance,
PreFunction&& pre_fn,
PostFunction&& post_fn,
fixed_containers::in_out<PathNameChain> chain)
{
std::forward<PreFunction>(pre_fn)(std::as_const(*chain), std::forward<S>(instance));
// recurse into an element instance, construct a dummy instance at `begin()` if the range is
// empty
chain->push_back(ITERABLE_PATH_NAME);
bool constructed{false};
if (std::ranges::empty(instance))
{
std::ranges::construct_at(std::ranges::data(instance));
constructed = true;
}
for_each_path_dfs_helper(*std::ranges::data(instance),
std::forward<PreFunction>(pre_fn),
std::forward<PostFunction>(post_fn),
fixed_containers::in_out{*chain});
if (constructed)
{
std::ranges::destroy_at(std::ranges::data(instance));
}
chain->pop_back();
std::forward<PostFunction>(post_fn)(std::as_const(*chain), std::forward<S>(instance));
}

template <typename S, typename PreFunction, typename PostFunction>
requires(StrategyReflect<std::decay_t<S>>)
constexpr void for_each_path_dfs_helper(S&& instance,
PreFunction&& pre_fn,
PostFunction&& post_fn,
fixed_containers::in_out<PathNameChain> chain)
{
std::forward<PreFunction>(pre_fn)(std::as_const(*chain), std::forward<S>(instance));
reflection::for_each_field(
std::forward<S>(instance),
[&pre_fn, &post_fn, &chain]<typename T>(const std::string_view& name, T& field)
{
chain->push_back(name);
for_each_path_dfs_helper(field,
std::forward<PreFunction>(pre_fn),
std::forward<PostFunction>(post_fn),
fixed_containers::in_out{*chain});
chain->pop_back();
});
std::forward<PostFunction>(post_fn)(std::as_const(*chain), std::forward<S>(instance));
}

template <typename S, typename PreFunction, typename PostFunction>
requires(StrategyPrimitive<std::decay_t<S>>)
constexpr void for_each_path_dfs_helper(S&& instance,
PreFunction&& pre_fn,
PostFunction&& post_fn,
fixed_containers::in_out<PathNameChain> chain)
{
std::forward<PreFunction>(pre_fn)(std::as_const(*chain), std::forward<S>(instance));
std::forward<PostFunction>(post_fn)(std::as_const(*chain), std::forward<S>(instance));
}

template <typename S, typename PreFunction, typename PostFunction>
constexpr void for_each_path_dfs_helper(S&& /*instance*/,
PreFunction&& /*pre_fn*/,
PostFunction&& /*post_fn*/,
fixed_containers::in_out<PathNameChain> /*chain*/)
{
// TODO: cover or resolve all possibilities
// static_assert(std::is_same_v<S, void>, Not covered by current recursion strategies);
}

struct OptionalCallInterface
{
std::function<bool(const void*)> has_value;
Expand Down Expand Up @@ -726,29 +562,6 @@ constexpr std::string_view type_name_without_namespace()
return (pos != std::string_view::npos) ? str.substr(pos + 1) : str;
}

// This function iterates over all paths of a given struct and calls a pre and post function for
// each field.
// The function cannot be const because we need to create a dummy instance inplace for
// `std::optional`
template <typename S, typename PreFunction, typename PostFunction>
requires(struct_view_detail::StrategyCovered<std::decay_t<S>>)
constexpr void for_each_path_dfs(S&& instance, PreFunction&& pre_fn, PostFunction&& post_fn)
{
PathNameChain chain{};
struct_view_detail::for_each_path_dfs_helper(std::forward<S>(instance),
std::forward<PreFunction>(pre_fn),
std::forward<PostFunction>(post_fn),
fixed_containers::in_out{chain});
}

template <typename S, typename Function>
requires(struct_view_detail::StrategyCovered<std::decay_t<S>>)
constexpr void for_each_path_dfs(S&& instance, Function&& func)
{
for_each_path_dfs(
std::forward<S>(instance), std::forward<Function>(func), [](const auto&, auto&) {});
}

template <typename S>
constexpr std::size_t path_count_of(S&& instance = {})
{
Expand Down
4 changes: 2 additions & 2 deletions test/struct_view_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ TEST(StructView, SubStructViewOfEnum)
{
static_assert(struct_view_detail::EnumValue<EnumSuperEnum>);
static_assert(struct_view_detail::EnumView<std::string_view>);
static_assert(!struct_view_detail::StrategyIterable<std::string_view>);
static_assert(!recursive_reflection_detail::StrategyIterable<std::string_view>);

auto enum_super_struct_1 = EnumSuperStruct{};
auto enum_sub_struct_1 = EnumSubStruct{};
Expand Down Expand Up @@ -793,7 +793,7 @@ struct NoRecurse<test_namespace::PrivateClass> : std::true_type
{
};

} // namespace fixed_containers::struct_view_detail
} // namespace fixed_containers::recursive_reflection_detail

TEST(StructView, NoRecurse)
{
Expand Down

0 comments on commit ad20f33

Please sign in to comment.