diff --git a/BUILD.bazel b/BUILD.bazel index 4f176247..f481e366 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -855,6 +855,27 @@ cc_library( copts = ["-std=c++20"], ) +cc_library( + name = "sub_struct_view", + hdrs = ["include/fixed_containers/sub_struct_view.hpp"], + includes = includes_config(), + strip_include_prefix = strip_include_prefix_config(), + deps = [ + ":assert_or_abort", + ":fixed_vector", + ":fixed_map", + ":fixed_set", + ":iterator_utils", + ":memory", + ":in_out", + ":out", + ":random_access_iterator", + ":reflection", + ":type_name" + ], + copts = ["-std=c++20"], +) + cc_library( name = "tuples", hdrs = [ @@ -1588,6 +1609,20 @@ cc_test( copts = ["-std=c++20"], ) +cc_test( + name = "sub_struct_view_test", + srcs = ["test/sub_struct_view_test.cpp"], + deps = [ + ":fixed_vector", + ":out", + ":sub_struct_view", + ":source_location", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], + copts = ["-std=c++20"], +) + cc_test( name = "tuples_test", srcs = ["test/tuples_test.cpp"], diff --git a/CMakeLists.txt b/CMakeLists.txt index bc900dd9..34408201 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,6 +223,8 @@ if(BUILD_TESTS) add_test_dependencies(stack_adapter_test) add_executable(string_literal_test test/string_literal_test.cpp) add_test_dependencies(string_literal_test) + add_executable(sub_struct_view_test test/sub_struct_view_test.cpp) + add_test_dependencies(sub_struct_view_test) add_executable(tuples_test test/tuples_test.cpp) add_test_dependencies(tuples_test) add_executable(type_name_test test/type_name_test.cpp) diff --git a/include/fixed_containers/index_or_value_storage.hpp b/include/fixed_containers/index_or_value_storage.hpp index cdeb54e1..79d3a32e 100644 --- a/include/fixed_containers/index_or_value_storage.hpp +++ b/include/fixed_containers/index_or_value_storage.hpp @@ -17,7 +17,7 @@ union IndexOrValueStorage explicit constexpr IndexOrValueStorage(const T& var) : value{var} { } explicit constexpr IndexOrValueStorage(T&& var) : value{std::move(var)} { } template - explicit constexpr IndexOrValueStorage(std::in_place_t /*unused*/, Args&&... args) : value(std::forward(args)...) { } + explicit constexpr IndexOrValueStorage(std::in_place_t /*unused*/, Args&&... args) : value{std::forward(args)...} { } constexpr IndexOrValueStorage(const IndexOrValueStorage&) requires TriviallyCopyConstructible = default; constexpr IndexOrValueStorage(IndexOrValueStorage&&) noexcept requires TriviallyMoveConstructible = default; @@ -61,7 +61,7 @@ union IndexOrValueStorage explicit constexpr IndexOrValueStorage(const T& var) : value{var} { } explicit constexpr IndexOrValueStorage(T&& var) : value{std::move(var)} { } template - explicit constexpr IndexOrValueStorage(std::in_place_t /*unused*/, Args&&... args) : value(std::forward(args)...) { } + explicit constexpr IndexOrValueStorage(std::in_place_t /*unused*/, Args&&... args) : value{std::forward(args)...} { } // clang-format on constexpr IndexOrValueStorage(const IndexOrValueStorage&) = default; constexpr IndexOrValueStorage(IndexOrValueStorage&&) noexcept = default; diff --git a/include/fixed_containers/memory.hpp b/include/fixed_containers/memory.hpp index 27e225a2..377da514 100644 --- a/include/fixed_containers/memory.hpp +++ b/include/fixed_containers/memory.hpp @@ -34,4 +34,15 @@ constexpr void destroy_and_construct_at_address_of(T& ref, Args&&... args) construct_at_address_of(ref, std::forward(args)...); } +template +const std::byte* addressof_as_const_byte_ptr(T& ref) +{ + return reinterpret_cast(std::addressof(ref)); +} +template +std::byte* addressof_as_mutable_byte_ptr(T& ref) +{ + return reinterpret_cast(std::addressof(ref)); +} + } // namespace fixed_containers::memory diff --git a/include/fixed_containers/optional_storage.hpp b/include/fixed_containers/optional_storage.hpp index dd96237c..ad3b4d32 100644 --- a/include/fixed_containers/optional_storage.hpp +++ b/include/fixed_containers/optional_storage.hpp @@ -22,7 +22,7 @@ union OptionalStorage explicit constexpr OptionalStorage(const T& var) : value{var} { } explicit constexpr OptionalStorage(T&& var) : value{std::move(var)} { } template - explicit constexpr OptionalStorage(std::in_place_t /*unused*/, Args&&... args) : value(std::forward(args)...) { } + explicit constexpr OptionalStorage(std::in_place_t /*unused*/, Args&&... args) : value{std::forward(args)...} { } constexpr OptionalStorage(const OptionalStorage&) requires TriviallyCopyConstructible = default; constexpr OptionalStorage(OptionalStorage&&) noexcept requires TriviallyMoveConstructible = default; @@ -70,7 +70,7 @@ union OptionalStorage explicit constexpr OptionalStorage(const T& var) : value{var} { } explicit constexpr OptionalStorage(T&& var) : value{std::move(var)} { } template - explicit constexpr OptionalStorage(std::in_place_t /*unused*/, Args&&... args) : value(std::forward(args)...) { } + explicit constexpr OptionalStorage(std::in_place_t /*unused*/, Args&&... args) : value{std::forward(args)...} { } // clang-format on constexpr OptionalStorage(const OptionalStorage&) = default; diff --git a/include/fixed_containers/sub_struct_view.hpp b/include/fixed_containers/sub_struct_view.hpp new file mode 100644 index 00000000..6a447b0f --- /dev/null +++ b/include/fixed_containers/sub_struct_view.hpp @@ -0,0 +1,544 @@ +#pragma once + +#include "fixed_containers/assert_or_abort.hpp" +#include "fixed_containers/fixed_map.hpp" +#include "fixed_containers/fixed_set.hpp" +#include "fixed_containers/fixed_vector.hpp" +#include "fixed_containers/in_out.hpp" +#include "fixed_containers/iterator_utils.hpp" +#include "fixed_containers/memory.hpp" +#include "fixed_containers/out.hpp" +#include "fixed_containers/random_access_iterator.hpp" +#include "fixed_containers/reflection.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Terminologies + * + * A Path: + * A `PathNameChain` is a sequence of struct field names that leads to a terminal field in the + * struct, with a caveat that when encountering an iterable, we do not include the index as part of + * the path, instead we use `data` to represent all the elements in the iterable. + * `for_each_path_dfs` is a recursive function that iterates over all the paths in the struct. + * + * Accessing a field by path: + * We defer all the indexing to the end of the path, + * where we can then use the `offset.get_offset(Indices indices)` function to get the offset of the + * field. + * + * Sub struct view: + * `sub_struct_view_of` create a view of the super struct object in the sub struct object. + * Currently, `sub_struct_view_of` employs a greedy strategy and updates all the indices for all + * paths at once. This can be improved for some usecases by using a lazy evaluation strategy, where + * we only update the indices when we need to. Users will specify a `ContiguousRangeSubStructView` + * instead of an array to denote the need of lazy evaluation. + * + * TODO: `ContiguousRangeSubStructView` currently only supports flat structs. + * To support partial lazy evaluation, use `PathPropertiesTree` instead of `PathPropertiesMap` + */ + +namespace fixed_containers::sub_struct_view_detail +{ + +inline constexpr std::size_t MAX_PATH_LENGTH = 16; +inline constexpr std::size_t MAX_DIM = 5; +inline constexpr std::string_view ITERABLE_PATH_NAME = "data[:]"; +inline constexpr std::string_view PATH_DELIMITER = "."; + +using PathNameChain = FixedVector; + +struct Dimension +{ + std::size_t stride{}; + std::size_t size{}; +}; + +template +using Dimensions = FixedVector; + +template +using Indices = FixedVector; + +template +struct Offset +{ + using Dimensions = Dimensions; + using Indices = Indices; + + std::size_t base_offset{}; + Dimensions dimensions{}; + + [[nodiscard]] auto get_offset(Indices indices) const + { + auto stride_view = dimensions | std::views::transform(&Dimension::stride); + return std::inner_product( + std::begin(indices), std::end(indices), std::begin(stride_view), base_offset); + } + + constexpr bool operator==(const Offset&) const = default; +}; + +// Recursion Strategy Concepts +template +concept Iterable = std::ranges::sized_range && std::ranges::contiguous_range; + +template +concept NonTerminal = reflection::Reflectable || Iterable; + +template +concept Terminal = !NonTerminal; + +template +concept Branch = reflection::Reflectable && !Iterable; + +template + requires(Iterable>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain); +template + requires(Branch>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain); +template + requires(Terminal>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain); +template +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain); + +template + requires(Iterable>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain) +{ + pre_fn(std::as_const(*chain), instance); + chain->push_back(ITERABLE_PATH_NAME); + for_each_path_dfs_helper(*instance.data(), pre_fn, post_fn, fixed_containers::in_out{*chain}); + chain->pop_back(); + post_fn(std::as_const(*chain), instance); +} + +template + requires(Branch>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain) +{ + pre_fn(std::as_const(*chain), instance); + reflection::for_each_field( + instance, + [&pre_fn, &post_fn, &chain](const std::string_view& name, T& field) + { + chain->push_back(name); + for_each_path_dfs_helper(field, pre_fn, post_fn, fixed_containers::in_out{*chain}); + chain->pop_back(); + }); + post_fn(std::as_const(*chain), instance); +} + +template + requires(Terminal>) +constexpr void for_each_path_dfs_helper(S&& instance, + PreFunction&& pre_fn, + PostFunction&& post_fn, + fixed_containers::in_out chain) +{ + pre_fn(std::as_const(*chain), instance); + post_fn(std::as_const(*chain), instance); +} + +template +constexpr void for_each_path_dfs_helper(S&& /*instance*/, + PreFunction&& /*pre_fn*/, + PostFunction&& /*post_fn*/, + fixed_containers::in_out /*chain*/) +{ + static_assert(std::is_same_v, "Unreachable Fallback"); +} + +// template function that expands to MAX_DIM nested loops for iterating all indices +template +constexpr void for_each_index_helper(const Offset& offset, + auto&& func, + Indices& indices) +{ + // DIM == std::size(indices) + if (DIM == std::size(offset.dimensions)) + { + func(indices); + return; + } + + for (std::size_t i = 0; i < offset.dimensions[DIM].size; ++i) + { + indices.push_back(i); + if constexpr (DIM < MAXIMUM_SIZE) + { + for_each_index_helper(offset, func, indices); + } + indices.pop_back(); + } +} + +// runtime version of offsetof in +template +std::size_t get_pointer_distance(Instance&& instance, Field&& field) +{ + const std::byte* instance_ptr = memory::addressof_as_const_byte_ptr(instance); + const std::byte* field_ptr = memory::addressof_as_const_byte_ptr(field); + assert_or_abort(instance_ptr <= field_ptr); + return static_cast(std::distance(instance_ptr, field_ptr)); +} + +} // namespace fixed_containers::sub_struct_view_detail + +namespace fixed_containers::sub_struct_view +{ + +using PathNameChain = sub_struct_view_detail::PathNameChain; +using Dimension = sub_struct_view_detail::Dimension; +using Dimensions = sub_struct_view_detail::Dimensions; +using Indices = sub_struct_view_detail::Indices; +using Offset = sub_struct_view_detail::Offset; + +enum class StructTreeNodeType +{ + BRANCH, + TERMINAL, + ITERABLE, +}; + +struct PathProperties +{ + StructTreeNodeType type{}; + Offset offset{}; + + constexpr bool operator==(const PathProperties&) const = default; +}; + +// This function iterates over all paths of a given struct and calls a pre and post function for +// each field. +template + requires(reflection::Reflectable>) +constexpr void for_each_path_dfs(S&& instance, PreFunction&& pre_fn, PostFunction&& post_fn) +{ + PathNameChain chain{}; + sub_struct_view_detail::for_each_path_dfs_helper( + instance, pre_fn, post_fn, fixed_containers::in_out{chain}); +} + +template +constexpr std::size_t path_count_of() +{ + std::size_t count = 0; + for_each_path_dfs(S{}, [&count](const auto&, auto&) { ++count; }, [](const auto&, auto&) {}); + return count; +} + +template +using PathPropertiesMap = FixedMap()>; +template +using PathSet = FixedSet()>; + +inline PathNameChain path_from_string(const std::string_view& path_name_chain_string) +{ + auto view_of_string_view = + path_name_chain_string | std::views::split(sub_struct_view_detail::PATH_DELIMITER) | + std::views::transform( + [](auto&& name) + { return std::string_view(std::ranges::begin(name), std::ranges::size(name)); }); + return PathNameChain(std::ranges::begin(view_of_string_view), + std::ranges::end(view_of_string_view)); +} + +template +auto extract_paths_of(const S& instance = {}) +{ + PathSet paths{}; + + for_each_path_dfs( + instance, + [&](const PathNameChain& chain, const F& /*field*/) { paths.insert(chain); }, + [&](const PathNameChain&, const F&) {}); + return paths; +} + +template +auto extract_path_properties_of_filtered( + const S& instance, const std::optional& registered_set = std::nullopt) +{ + PathPropertiesMap paths{}; + Dimensions dimensions{}; + + for_each_path_dfs( + instance, + [&](const PathNameChain& chain, const F& field) + { + if (registered_set.has_value() && !registered_set.value().contains(chain)) + { + return; + } + if constexpr (sub_struct_view_detail::Terminal) + { + auto [_, was_inserted] = paths.try_emplace( + chain, + PathProperties{ + .type = StructTreeNodeType::TERMINAL, + .offset = {.base_offset = sub_struct_view_detail::get_pointer_distance( + instance, field), + .dimensions = dimensions}, + }); + assert_or_abort(was_inserted); + } + else if constexpr (sub_struct_view_detail::Iterable) + { + dimensions.push_back({ + .stride = sizeof(std::ranges::range_value_t), + .size = std::size(field), + }); + auto [_, was_inserted] = paths.try_emplace( + chain, + PathProperties{ + .type = StructTreeNodeType::ITERABLE, + .offset = {.base_offset = sub_struct_view_detail::get_pointer_distance( + instance, field), + .dimensions = dimensions}, + }); + assert_or_abort(was_inserted); + } + else if constexpr (sub_struct_view_detail::Branch) + { + // Branch nodes will not be part of path properties. + // They can be used naturally inside of the sub struct. + } + }, + [&](const PathNameChain& /*chain*/, const F& /*field*/) + { + if constexpr (sub_struct_view_detail::Iterable) + { + dimensions.pop_back(); + } + }); + return paths; +} + +template +auto extract_path_properties_of(const S& instance = {}) +{ + return extract_path_properties_of_filtered>(instance, std::nullopt); +} + +void for_each_index(const Offset& offset, auto&& func) +{ + Indices indices; + for_each_index_helper<0>(offset, func, indices); +} + +template +void sub_struct_view_of(const std::byte* base_super_struct_pointer, + const SuperProperties& super_struct_path_properties, + std::byte* base_sub_struct_pointer, + const SubProperties& sub_struct_path_properties) +{ + for (const auto& [path, path_properties] : sub_struct_path_properties) + { + Offset super_struct_offset = super_struct_path_properties.at(path).offset; + Offset sub_struct_offset = sub_struct_path_properties.at(path).offset; + + for_each_index( + sub_struct_offset, + [&](const auto& indices) + { + const std::byte* super_struct_field_ptr = + std::next(base_super_struct_pointer, + static_cast(super_struct_offset.get_offset(indices))); + std::byte* sub_struct_field_ptr = + std::next(base_sub_struct_pointer, + static_cast(sub_struct_offset.get_offset(indices))); + *reinterpret_cast(sub_struct_field_ptr) = + reinterpret_cast(super_struct_field_ptr); + }); + } +} + +template +void sub_struct_view_of(const Super& super_struct, + const SuperProperties& super_struct_path_properties, + out out_sub_struct, + const SubProperties& sub_struct_path_properties) +{ + const std::byte* base_super_struct_pointer = memory::addressof_as_const_byte_ptr(super_struct); + std::byte* base_sub_struct_pointer = memory::addressof_as_mutable_byte_ptr(*out_sub_struct); + + return sub_struct_view_of(base_super_struct_pointer, + super_struct_path_properties, + base_sub_struct_pointer, + sub_struct_path_properties); +} + +template +class ContiguousRangeSubStructView +{ + struct AccessingInfo + { + PathPropertiesMap sub_struct_path_properties{}; + PathPropertiesMap super_struct_path_properties{}; + std::byte* base_array_super_struct_ptr{}; + std::size_t stride{}; + std::size_t size{}; + }; + + static SubStruct create_view_at_offset(const AccessingInfo& accessing_info, + const std::size_t index) + { + assert_or_abort(index < accessing_info.size); + SubStruct instance{}; + std::byte* base_of_ith_entry = + std::next(accessing_info.base_array_super_struct_ptr, + static_cast(index * accessing_info.stride)); + sub_struct_view::sub_struct_view_of(base_of_ith_entry, + accessing_info.super_struct_path_properties, + memory::addressof_as_mutable_byte_ptr(instance), + accessing_info.sub_struct_path_properties); + return instance; + } + + using ReferenceType = SubStruct; + + class ReferenceProvider + { + private: + const AccessingInfo* accessing_info_; + std::size_t current_index_; + + public: + constexpr ReferenceProvider() noexcept + : ReferenceProvider{nullptr, 0} + { + } + + constexpr ReferenceProvider(const AccessingInfo& accessing_info, + const std::size_t& current_index) noexcept + : accessing_info_{&accessing_info} + , current_index_{current_index} + { + } + + constexpr void advance(const std::size_t n) noexcept { current_index_ += n; } + constexpr void recede(const std::size_t n) noexcept { current_index_ -= n; } + + [[nodiscard]] constexpr ReferenceType get() const noexcept + { + return create_view_at_offset(*accessing_info_, current_index_); + } + + constexpr bool operator==(const ReferenceProvider& other) const noexcept + { + assert_or_abort(accessing_info_ == other.accessing_info_); + return current_index_ == other.current_index_; + } + constexpr auto operator<=>(const ReferenceProvider& other) const noexcept + { + assert_or_abort(accessing_info_ == other.accessing_info_); + return current_index_ <=> other.current_index_; + } + + constexpr std::ptrdiff_t operator-(const ReferenceProvider& other) const noexcept + { + assert_or_abort(accessing_info_ == other.accessing_info_); + return static_cast(current_index_ - other.current_index_); + } + }; + + using IteratorType = RandomAccessIterator; + +public: + using const_reference = ReferenceType; + using const_iterator = IteratorType; + +private: + AccessingInfo accessing_info_; + +public: + ContiguousRangeSubStructView() + : accessing_info_{} + { + } + + template + ContiguousRangeSubStructView(SuperStructContainer& super_struct_container) + : accessing_info_{ + .sub_struct_path_properties = extract_path_properties_of(), + .super_struct_path_properties = {}, + .base_array_super_struct_ptr = + memory::addressof_as_mutable_byte_ptr(*super_struct_container.data()), + .stride = {}, + .size = super_struct_container.size(), + } + + { + using SuperStruct = typename SuperStructContainer::value_type; + auto super_struct_path_properties_all = extract_path_properties_of(); + for (const auto& [name, _] : accessing_info_.sub_struct_path_properties) + { + accessing_info_.super_struct_path_properties[name] = + super_struct_path_properties_all.at(name); + } + + accessing_info_.stride = sizeof(SuperStruct); + } + + [[nodiscard]] const_reference at(const std::size_t index) const + { + return create_view_at_offset(accessing_info_, index); + } + + [[nodiscard]] std::size_t size() const { return accessing_info_.size; } + + constexpr const_iterator begin() noexcept { return cbegin(); } + [[nodiscard]] constexpr const_iterator begin() const noexcept { return cbegin(); } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept + { + return create_const_iterator(0); + } + + constexpr const_iterator end() noexcept { return cend(); } + [[nodiscard]] constexpr const_iterator end() const noexcept { return cend(); } + [[nodiscard]] constexpr const_iterator cend() const noexcept + { + return create_const_iterator(accessing_info_.size); + } + +private: + [[nodiscard]] constexpr const_iterator create_const_iterator( + const std::size_t offset_from_start) const noexcept + { + return const_iterator{ReferenceProvider{accessing_info_, offset_from_start}}; + } +}; + +} // namespace fixed_containers::sub_struct_view diff --git a/include/fixed_containers/value_or_reference_storage.hpp b/include/fixed_containers/value_or_reference_storage.hpp index 41c0830e..ba52e0f4 100644 --- a/include/fixed_containers/value_or_reference_storage.hpp +++ b/include/fixed_containers/value_or_reference_storage.hpp @@ -14,7 +14,7 @@ struct ValueOrReferenceStorage template explicit constexpr ValueOrReferenceStorage(Args&&... args) - : value(std::forward(args)...) + : value{std::forward(args)...} { } diff --git a/test/sub_struct_view_test.cpp b/test/sub_struct_view_test.cpp new file mode 100644 index 00000000..a06080fd --- /dev/null +++ b/test/sub_struct_view_test.cpp @@ -0,0 +1,601 @@ +#if defined(__clang__) && __clang_major__ >= 15 + +#include "fixed_containers/sub_struct_view.hpp" + +#include "fixed_containers/fixed_vector.hpp" +#include "fixed_containers/out.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace fixed_containers::sub_struct_view +{ +namespace +{ + +[[maybe_unused]] std::ostream& operator<<(std::ostream& out_stream, const PathNameChain& chain) +{ + out_stream << '['; + if (!std::empty(chain)) + { + for (auto it = std::begin(chain); it != std::end(chain) - 1; ++it) + { + out_stream << *it << '.'; + } + out_stream << std::rbegin(chain)->data(); + } + out_stream << ']'; + return out_stream; +} + +struct FlatSuperStruct1 +{ + std::int8_t ignore1_dont_forget_alignment{}; + std::int64_t retain1{}; + std::int32_t ignore2{}; + std::int32_t retain2{}; + std::int16_t ignore3{}; +}; + +struct FlatSubStruct1 +{ + const std::int64_t* retain1; + const std::int32_t* retain2; +}; + +} // namespace + +TEST(SubStructView, GetPointerDistanceFlat) +{ + const FlatSuperStruct1 flat_super_struct_1{}; + EXPECT_EQ(8, + sub_struct_view_detail::get_pointer_distance(flat_super_struct_1, + flat_super_struct_1.retain1)); + EXPECT_EQ(20, + sub_struct_view_detail::get_pointer_distance(flat_super_struct_1, + flat_super_struct_1.retain2)); +} + +TEST(SubStructView, ExtractPathsOfFlat) +{ + { + auto paths = extract_paths_of(); + EXPECT_EQ(3, path_count_of()); + EXPECT_EQ(std::size(paths), path_count_of()); + EXPECT_TRUE(paths.contains(path_from_string(""))); + EXPECT_TRUE(paths.contains(path_from_string("retain1"))); + EXPECT_TRUE(paths.contains(path_from_string("retain2"))); + } +} + +TEST(SubStructView, ExtractPathPropertiesOfFlat) +{ + { + auto path_properties = extract_path_properties_of(); + + EXPECT_EQ(5, std::size(path_properties)); + EXPECT_EQ(0, + path_properties.at(path_from_string("ignore1_dont_forget_alignment")) + .offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("ignore1_dont_forget_alignment")).type); + + EXPECT_EQ(8, path_properties.at(path_from_string("retain1")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("retain1")).type); + + EXPECT_EQ(16, path_properties.at(path_from_string("ignore2")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("ignore2")).type); + + EXPECT_EQ(20, path_properties.at(path_from_string("retain2")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("retain2")).type); + + EXPECT_EQ(24, path_properties.at(path_from_string("ignore3")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("ignore3")).type); + } + { + const FlatSubStruct1 instance{}; + + auto path_properties = extract_path_properties_of(instance); + + EXPECT_EQ(2, path_properties.size()); + + EXPECT_EQ(0, path_properties.at(path_from_string("retain1")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("retain1")).type); + + EXPECT_EQ(8, path_properties.at(path_from_string("retain2")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("retain2")).type); + } +} + +TEST(SubStructView, SubStructViewOfFlat) +{ + const FlatSuperStruct1 flat_super_struct_1{}; + FlatSubStruct1 flat_sub_struct_1{}; + + auto super_struct_path_properties = extract_path_properties_of(flat_super_struct_1); + auto sub_struct_path_properties = extract_path_properties_of(flat_sub_struct_1); + + sub_struct_view_of(flat_super_struct_1, + super_struct_path_properties, + out{flat_sub_struct_1}, + sub_struct_path_properties); + + ASSERT_EQ(flat_sub_struct_1.retain1, &flat_super_struct_1.retain1); + ASSERT_EQ(flat_sub_struct_1.retain2, &flat_super_struct_1.retain2); +} + +namespace +{ +inline constexpr std::size_t TEST_ARRAY_SIZE = 3; + +struct PointXYZ +{ + std::int64_t x{}; + std::int64_t y{}; + std::int64_t z{}; +}; + +struct FlatSuperStruct2 +{ + std::int16_t ignore1{}; + std::array retain_array_1{}; + FixedVector retain_vec_2{}; + std::int32_t ignore2{}; +}; + +struct PointXZ +{ + const std::int64_t* z{}; + const std::int64_t* x{}; +}; + +struct FlatSubStruct2 +{ + ContiguousRangeSubStructView retain_array_1{}; + ContiguousRangeSubStructView retain_vec_2{}; +}; + +} // namespace + +TEST(ContiguousRangeSubStructView, OperatorAtFlat) +{ + FlatSuperStruct2 flat_super_struct_2{}; + FlatSubStruct2 flat_sub_struct_2{}; + flat_super_struct_2.retain_vec_2.resize(TEST_ARRAY_SIZE); + + flat_sub_struct_2.retain_array_1 = flat_super_struct_2.retain_array_1; + flat_sub_struct_2.retain_vec_2 = flat_super_struct_2.retain_vec_2; + + { + ASSERT_EQ(TEST_ARRAY_SIZE, flat_sub_struct_2.retain_array_1.size()); + + for (std::size_t i = 0; i < TEST_ARRAY_SIZE; ++i) + { + ASSERT_EQ(flat_sub_struct_2.retain_array_1.at(i).x, + &flat_super_struct_2.retain_array_1.at(i).x); + ASSERT_EQ(flat_sub_struct_2.retain_array_1.at(i).z, + &flat_super_struct_2.retain_array_1.at(i).z); + } + ASSERT_DEATH((void)flat_sub_struct_2.retain_array_1.at(TEST_ARRAY_SIZE), ""); + } + + { + ASSERT_EQ(TEST_ARRAY_SIZE, flat_sub_struct_2.retain_vec_2.size()); + + for (std::size_t i = 0; i < TEST_ARRAY_SIZE; ++i) + { + ASSERT_EQ(flat_sub_struct_2.retain_vec_2.at(i).x, + &flat_super_struct_2.retain_vec_2.at(i).x); + ASSERT_EQ(flat_sub_struct_2.retain_vec_2.at(i).z, + &flat_super_struct_2.retain_vec_2.at(i).z); + } + ASSERT_DEATH((void)flat_sub_struct_2.retain_vec_2.at(TEST_ARRAY_SIZE), ""); + } +} + +TEST(ContiguousRangeSubStructView, IterationFlat) +{ + FlatSuperStruct2 flat_super_struct_2{}; + FlatSubStruct2 flat_sub_struct_2{}; + flat_super_struct_2.retain_vec_2.resize(TEST_ARRAY_SIZE); + + flat_sub_struct_2.retain_array_1 = flat_super_struct_2.retain_array_1; + flat_sub_struct_2.retain_vec_2 = flat_super_struct_2.retain_vec_2; + + { + ASSERT_EQ(TEST_ARRAY_SIZE, flat_sub_struct_2.retain_array_1.size()); + + std::size_t counter = 0; + for (auto&& sub_struct : flat_sub_struct_2.retain_array_1) + { + ASSERT_EQ(sub_struct.x, &flat_super_struct_2.retain_array_1.at(counter).x); + ASSERT_EQ(sub_struct.z, &flat_super_struct_2.retain_array_1.at(counter).z); + counter++; + } + ASSERT_EQ(TEST_ARRAY_SIZE, counter); + } + { + ASSERT_EQ(TEST_ARRAY_SIZE, flat_sub_struct_2.retain_vec_2.size()); + + std::size_t counter = 0; + for (auto&& sub_struct : flat_sub_struct_2.retain_vec_2) + { + ASSERT_EQ(sub_struct.x, &flat_super_struct_2.retain_vec_2.at(counter).x); + ASSERT_EQ(sub_struct.z, &flat_super_struct_2.retain_vec_2.at(counter).z); + counter++; + } + ASSERT_EQ(TEST_ARRAY_SIZE, counter); + } +} + +namespace +{ + +struct NestedSuperStructLayer2 +{ + std::int16_t retain1{}; + std::int64_t retain2{}; +}; + +struct NestedSuperStructLayer1 +{ + std::int8_t alignment_check_1{}; + std::int64_t retain1{}; + NestedSuperStructLayer2 nested1{}; + std::int32_t ignore2{}; + NestedSuperStructLayer2 nested2{}; +}; + +struct NestedSubStructLayer2Usage1 +{ + const std::int16_t* retain1{}; +}; + +struct NestedSubStructLayer2Usage2 +{ + const std::int64_t* retain2{}; +}; + +struct NestedSubStructLayer1 +{ + const std::int64_t* retain1{}; + NestedSubStructLayer2Usage1 nested1{}; + NestedSubStructLayer2Usage2 nested2{}; +}; + +} // namespace + +TEST(SubStructView, GetPointerDistanceRecursive) +{ + const NestedSuperStructLayer1 nested_super_struct_1{}; + EXPECT_EQ(8, + sub_struct_view_detail::get_pointer_distance(nested_super_struct_1, + nested_super_struct_1.retain1)); + EXPECT_EQ(16, + sub_struct_view_detail::get_pointer_distance(nested_super_struct_1, + nested_super_struct_1.nested1.retain1)); + EXPECT_EQ(48, + sub_struct_view_detail::get_pointer_distance(nested_super_struct_1, + nested_super_struct_1.nested2.retain2)); +} + +TEST(SubStructView, ExtractPathsOfRecursive) +{ + { + auto paths = extract_paths_of(); + EXPECT_EQ(path_count_of(), std::size(paths)); + EXPECT_EQ(6, path_count_of()); + EXPECT_TRUE(paths.contains(path_from_string(""))); + EXPECT_TRUE(paths.contains(path_from_string("retain1"))); + EXPECT_TRUE(paths.contains(path_from_string("nested1"))); + EXPECT_TRUE(paths.contains(path_from_string("nested1.retain1"))); + EXPECT_TRUE(paths.contains(path_from_string("nested2"))); + EXPECT_TRUE(paths.contains(path_from_string("nested2.retain2"))); + } +} + +TEST(SubStructView, ExtractPathPropertiesOfRecursive) +{ + { + auto nested_sub_struct_1 = NestedSubStructLayer1{}; + auto path_properties = extract_path_properties_of(); + + EXPECT_EQ(3, path_properties.size()); + + EXPECT_EQ(0, path_properties.at(path_from_string("retain1")).offset.base_offset); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("retain1")).type); + + EXPECT_EQ(path_properties.at(path_from_string("nested1.retain1")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + nested_sub_struct_1, nested_sub_struct_1.nested1.retain1)); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("nested1.retain1")).type); + + EXPECT_EQ(path_properties.at(path_from_string("nested2.retain2")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + nested_sub_struct_1, nested_sub_struct_1.nested2.retain2)); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("nested2.retain2")).type); + } +} + +TEST(SubStructView, SubStructViewOfRecursive) +{ + const NestedSuperStructLayer1 nested_super_struct_1{}; + NestedSubStructLayer1 nested_sub_struct_1{}; + + auto super_struct_path_properties = extract_path_properties_of(nested_super_struct_1); + auto sub_struct_path_properties = extract_path_properties_of(nested_sub_struct_1); + + sub_struct_view_of(nested_super_struct_1, + super_struct_path_properties, + out{nested_sub_struct_1}, + sub_struct_path_properties); + + ASSERT_EQ(nested_sub_struct_1.retain1, &nested_super_struct_1.retain1); + ASSERT_EQ(nested_sub_struct_1.nested1.retain1, &nested_super_struct_1.nested1.retain1); + ASSERT_EQ(nested_sub_struct_1.nested2.retain2, &nested_super_struct_1.nested2.retain2); +} + +namespace +{ + +struct ArrayTestSuperStructLayer2 +{ + std::int8_t alignment_check_1{}; + std::array arr{}; + FixedVector vec{ + FixedVector(TEST_ARRAY_SIZE)}; + std::int8_t alignment_check_2{}; +}; + +struct ArrayTestSuperStructLayer1 +{ + std::int8_t alignment_check_1{}; + std::int64_t ignored{}; + std::array arr{}; + std::int8_t alignment_check_2{}; + FixedVector vec{ + FixedVector(TEST_ARRAY_SIZE)}; + std::array, TEST_ARRAY_SIZE> matrix{}; +}; + +struct ArrayTestSubStructLayer2 +{ + std::array arr{}; + // use std::array for accessing FixedVector + std::array vec{}; +}; + +struct ArrayTestSubStructLayer1 +{ + std::array arr{}; + std::array vec{}; + std::array, TEST_ARRAY_SIZE> matrix{}; +}; + +} // namespace + +TEST(SubStructView, GetPointerDistanceRecursiveWithArray) +{ + const ArrayTestSuperStructLayer1 array_test_super_struct_1{}; + EXPECT_EQ(8 + 8, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.arr)); + EXPECT_EQ(8 + 8 + TEST_ARRAY_SIZE * sizeof(ArrayTestSuperStructLayer2) + 8, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.vec)); + EXPECT_EQ(8 + 8 + TEST_ARRAY_SIZE * sizeof(ArrayTestSuperStructLayer2) + 8 + + (8 + TEST_ARRAY_SIZE * sizeof(ArrayTestSuperStructLayer2)), + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.matrix)); +} + +TEST(SubStructView, ExtractPathsOfRecursiveWithArray) +{ + { + auto paths = extract_paths_of(); + EXPECT_EQ(path_count_of(), std::size(paths)); + EXPECT_EQ(16, path_count_of()); + EXPECT_TRUE(paths.contains(path_from_string(""))); + EXPECT_TRUE(paths.contains(path_from_string("arr.data[:].vec.data[:]"))); + EXPECT_TRUE(paths.contains(path_from_string("vec.data[:].arr.data[:]"))); + EXPECT_TRUE(paths.contains(path_from_string("matrix.data[:].data[:]"))); + } +} + +TEST(SubStructView, ExtractPathPropertiesOfRecursiveWithArray) +{ + { + auto array_test_super_struct_1 = ArrayTestSuperStructLayer1{}; + auto path_properties = extract_path_properties_of(array_test_super_struct_1); + + EXPECT_EQ(path_properties.size(), 20); + + // std::array + + // 1st dimension + EXPECT_EQ(path_properties.at(path_from_string("arr")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.arr)); + EXPECT_EQ(StructTreeNodeType::ITERABLE, path_properties.at(path_from_string("arr")).type); + EXPECT_EQ(1, path_properties.at(path_from_string("arr")).offset.dimensions.size()); + EXPECT_EQ(TEST_ARRAY_SIZE, + path_properties.at(path_from_string("arr")).offset.dimensions[0].size); + EXPECT_EQ(sizeof(ArrayTestSuperStructLayer2), + path_properties.at(path_from_string("arr")).offset.dimensions[0].stride); + + EXPECT_DEATH((void)path_properties.at(path_from_string("arr.data[:]")), ""); + + // 2nd dimension + EXPECT_EQ(path_properties.at(path_from_string("arr.data[:].arr")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + array_test_super_struct_1, array_test_super_struct_1.arr[0].arr)); + EXPECT_EQ(StructTreeNodeType::ITERABLE, + path_properties.at(path_from_string("arr.data[:].arr")).type); + EXPECT_EQ(2, + path_properties.at(path_from_string("arr.data[:].arr")).offset.dimensions.size()); + EXPECT_EQ( + TEST_ARRAY_SIZE, + path_properties.at(path_from_string("arr.data[:].arr")).offset.dimensions[0].size); + EXPECT_EQ( + path_properties.at(path_from_string("arr.data[:].arr")).offset.dimensions[0].stride, + sizeof(ArrayTestSuperStructLayer2)); + EXPECT_EQ( + TEST_ARRAY_SIZE, + path_properties.at(path_from_string("arr.data[:].arr")).offset.dimensions[1].size); + EXPECT_EQ( + path_properties.at(path_from_string("arr.data[:].arr")).offset.dimensions[1].stride, + sizeof(std::int16_t)); + + // terminal + EXPECT_EQ( + path_properties.at(path_from_string("arr.data[:].arr.data[:]")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.arr[0].arr[0])); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("arr.data[:].arr.data[:]")).type); + EXPECT_EQ(path_properties.at(path_from_string("arr.data[:].arr.data[:]")) + .offset.dimensions.size(), + 2); + + // FixedVector + + // 1st dimension + EXPECT_EQ(path_properties.at(path_from_string("vec")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.vec)); + EXPECT_EQ(StructTreeNodeType::ITERABLE, path_properties.at(path_from_string("vec")).type); + EXPECT_EQ(1, path_properties.at(path_from_string("vec")).offset.dimensions.size()); + EXPECT_EQ(TEST_ARRAY_SIZE, + path_properties.at(path_from_string("vec")).offset.dimensions[0].size); + EXPECT_EQ(sizeof(ArrayTestSuperStructLayer2), + path_properties.at(path_from_string("vec")).offset.dimensions[0].stride); + + EXPECT_DEATH((void)path_properties.at(path_from_string("vec.data[:]")), ""); + + // 2nd dimension + EXPECT_EQ(path_properties.at(path_from_string("vec.data[:].arr")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + array_test_super_struct_1, array_test_super_struct_1.vec[0].arr)); + EXPECT_EQ(StructTreeNodeType::ITERABLE, + path_properties.at(path_from_string("vec.data[:].arr")).type); + EXPECT_EQ(2, + path_properties.at(path_from_string("vec.data[:].arr")).offset.dimensions.size()); + EXPECT_EQ( + TEST_ARRAY_SIZE, + path_properties.at(path_from_string("vec.data[:].arr")).offset.dimensions[0].size); + EXPECT_EQ( + path_properties.at(path_from_string("vec.data[:].arr")).offset.dimensions[0].stride, + sizeof(ArrayTestSuperStructLayer2)); + EXPECT_EQ( + TEST_ARRAY_SIZE, + path_properties.at(path_from_string("vec.data[:].arr")).offset.dimensions[1].size); + EXPECT_EQ( + path_properties.at(path_from_string("vec.data[:].arr")).offset.dimensions[1].stride, + sizeof(std::int16_t)); + + // terminal + EXPECT_EQ( + path_properties.at(path_from_string("vec.data[:].arr.data[:]")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.vec[0].arr[0])); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("vec.data[:].arr.data[:]")).type); + EXPECT_EQ(path_properties.at(path_from_string("vec.data[:].arr.data[:]")) + .offset.dimensions.size(), + 2); + + // matrix (2d std::array) + + // 1st dimension + EXPECT_EQ(path_properties.at(path_from_string("matrix")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance(array_test_super_struct_1, + array_test_super_struct_1.matrix)); + EXPECT_EQ(StructTreeNodeType::ITERABLE, + path_properties.at(path_from_string("matrix")).type); + EXPECT_EQ(1, path_properties.at(path_from_string("matrix")).offset.dimensions.size()); + EXPECT_EQ(TEST_ARRAY_SIZE, + path_properties.at(path_from_string("matrix")).offset.dimensions[0].size); + EXPECT_EQ(TEST_ARRAY_SIZE * sizeof(std::int64_t), + path_properties.at(path_from_string("matrix")).offset.dimensions[0].stride); + + // 2nd dimension + EXPECT_EQ(path_properties.at(path_from_string("matrix.data[:]")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + array_test_super_struct_1, array_test_super_struct_1.matrix[0])); + EXPECT_EQ(StructTreeNodeType::ITERABLE, + path_properties.at(path_from_string("matrix.data[:]")).type); + EXPECT_EQ(2, + path_properties.at(path_from_string("matrix.data[:]")).offset.dimensions.size()); + EXPECT_EQ(TEST_ARRAY_SIZE, + path_properties.at(path_from_string("matrix.data[:]")).offset.dimensions[0].size); + EXPECT_EQ( + TEST_ARRAY_SIZE * sizeof(std::int64_t), + path_properties.at(path_from_string("matrix.data[:]")).offset.dimensions[0].stride); + EXPECT_EQ(TEST_ARRAY_SIZE, + path_properties.at(path_from_string("matrix.data[:]")).offset.dimensions[1].size); + EXPECT_EQ( + sizeof(std::int64_t), + path_properties.at(path_from_string("matrix.data[:]")).offset.dimensions[1].stride); + + // terminal + EXPECT_EQ(path_properties.at(path_from_string("matrix.data[:].data[:]")).offset.base_offset, + sub_struct_view_detail::get_pointer_distance( + array_test_super_struct_1, array_test_super_struct_1.matrix[0][0])); + EXPECT_EQ(StructTreeNodeType::TERMINAL, + path_properties.at(path_from_string("matrix.data[:].data[:]")).type); + EXPECT_EQ( + path_properties.at(path_from_string("matrix.data[:].data[:]")).offset.dimensions.size(), + 2); + } +} + +TEST(SubStructView, SubStructViewOfRecursiveWithArray) +{ + ArrayTestSuperStructLayer1 array_test_super_struct_1{}; + ArrayTestSubStructLayer1 array_test_sub_struct_1{}; + + auto paths = extract_paths_of(array_test_sub_struct_1); + auto super_struct_path_properties = extract_path_properties_of_filtered( + array_test_super_struct_1, std::optional>{paths}); + auto sub_struct_path_properties = extract_path_properties_of(array_test_sub_struct_1); + + sub_struct_view_of(array_test_super_struct_1, + super_struct_path_properties, + out{array_test_sub_struct_1}, + sub_struct_path_properties); + + for (std::size_t i = 0; i < TEST_ARRAY_SIZE; ++i) + { + for (std::size_t j = 0; j < TEST_ARRAY_SIZE; ++j) + { + ASSERT_TRUE(array_test_sub_struct_1.arr[i].arr[j] == + &array_test_super_struct_1.arr[i].arr[j]); + ASSERT_TRUE(array_test_sub_struct_1.arr[i].vec[j] == + &array_test_super_struct_1.arr[i].vec[j]); + ASSERT_TRUE(array_test_sub_struct_1.vec[i].arr[j] == + &array_test_super_struct_1.vec[i].arr[j]); + ASSERT_TRUE(array_test_sub_struct_1.vec[i].vec[j] == + &array_test_super_struct_1.vec[i].vec[j]); + ASSERT_TRUE(array_test_sub_struct_1.matrix[i][j] == + &array_test_super_struct_1.matrix[i][j]); + } + } +} + +} // namespace fixed_containers::sub_struct_view + +#endif