diff --git a/BUILD.bazel b/BUILD.bazel index ec3c4308..238152cd 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -329,6 +329,7 @@ cc_library( includes = ["include"], deps = [ ":concepts", + ":fixed_stack", ":fixed_vector", ":in_out", ], diff --git a/include/fixed_containers/reflection.hpp b/include/fixed_containers/reflection.hpp index 1cafd876..46b9d7bb 100644 --- a/include/fixed_containers/reflection.hpp +++ b/include/fixed_containers/reflection.hpp @@ -1,6 +1,7 @@ #pragma once #include "fixed_containers/concepts.hpp" +#include "fixed_containers/fixed_stack.hpp" #include "fixed_containers/fixed_vector.hpp" #include "fixed_containers/in_out.hpp" @@ -19,28 +20,101 @@ namespace fixed_containers::reflection_detail template concept Reflectable = std::is_class_v && ConstexprDefaultConstructible; -class DepthTracker +enum class LayerType { - int depth_{-1}; + ENCLOSING_FIELD, + BASE_CLASS, +}; + +struct FieldAsString +{ + std::string_view type_name; + std::string_view name; +}; + +inline constexpr std::string_view NULL_FIELD_TYPE_NAME{}; + +template +class LayerTracker +{ + FixedStack layer_type_stack_; + FixedStack nesting_stack_; + FixedStack inheritance_stack_; + +public: + constexpr LayerTracker() + : layer_type_stack_{} + , nesting_stack_{} + , inheritance_stack_{} + { + } public: - [[nodiscard]] constexpr int depth() const + [[nodiscard]] constexpr bool is_null_layer() const { return layer_type_stack_.empty(); } + [[nodiscard]] constexpr FieldAsString current_enclosing_field() const { - assert(depth_ != -1); - return depth_; + return nesting_stack_.top(); + } + [[nodiscard]] constexpr std::string_view current_providing_base_class() const + { + return inheritance_stack_.empty() ? NULL_FIELD_TYPE_NAME : inheritance_stack_.top(); } - [[nodiscard]] constexpr bool is_null_depth() const { return depth_ == -1; } - constexpr void update_depth(const std::string_view fmt) + template + constexpr void update_layer(const std::string_view fmt, Args&&... args) { - const char special_char = fmt.at(fmt.size() - 2); - if (special_char == '{') + if (fmt == "%s") { - ++depth_; + if constexpr (sizeof...(args) == 1) + { + auto as_tuple = std::tuple{std::forward(args)...}; + layer_type_stack_.push(LayerType::ENCLOSING_FIELD); + nesting_stack_.push({std::get<0>(as_tuple), ""}); + return; + } + else + { + assert(false); + } } - if (special_char == '}') + + if (fmt == "%s%s") { - --depth_; + if constexpr (sizeof...(args) == 2) + { + auto as_tuple = std::tuple{std::forward(args)...}; + layer_type_stack_.push(LayerType::BASE_CLASS); + inheritance_stack_.push(std::get<1>(as_tuple)); + return; + } + else + { + assert(false); + } + } + + if (fmt.at(fmt.size() - 2) == '}') + { + switch (layer_type_stack_.top()) + { + case LayerType::ENCLOSING_FIELD: + nesting_stack_.pop(); + break; + case LayerType::BASE_CLASS: + inheritance_stack_.pop(); + break; + } + layer_type_stack_.pop(); + } + } + + constexpr void update_layer_that_is_also_an_entry(const std::string_view fmt, + const FieldAsString& field) + { + if (fmt == "%s%s %s =") + { + layer_type_stack_.push(LayerType::ENCLOSING_FIELD); + nesting_stack_.push(field); } } }; @@ -48,56 +122,78 @@ class DepthTracker class FieldEntry { private: - std::string_view field_type_name_; - std::string_view field_name_; - int depth_; + FieldAsString field_; + FieldAsString enclosing_field_; + std::string_view providing_base_class_name_; public: - constexpr FieldEntry(const std::string_view& field_type_name, - const std::string_view& field_name, - const int depth) noexcept - : field_type_name_{field_type_name} - , field_name_{field_name} - , depth_{depth} + constexpr FieldEntry(const FieldAsString& field, + const FieldAsString& enclosing_field, + std::string_view providing_base_class_name) noexcept + : field_{field} + , enclosing_field_(enclosing_field) + , providing_base_class_name_(providing_base_class_name) { } public: - [[nodiscard]] constexpr std::string_view field_type_name() const { return field_type_name_; } - [[nodiscard]] constexpr std::string_view field_name() const { return field_name_; } - [[nodiscard]] constexpr int depth() const { return depth_; } + [[nodiscard]] constexpr std::string_view field_type_name() const { return field_.type_name; } + [[nodiscard]] constexpr std::string_view field_name() const { return field_.name; } + [[nodiscard]] constexpr std::string_view enclosing_field_type_name() const + { + return enclosing_field_.type_name; + } + [[nodiscard]] constexpr std::string_view enclosing_field_name() const + { + return enclosing_field_.name; + } + [[nodiscard]] constexpr std::string_view providing_base_class_name() const + { + return providing_base_class_name_; + } }; template Func> constexpr void for_each_field_entry(const T& instance, Func func) { - auto converter = [&func]( - in_out depth_tracker, const char* const fmt, Args&&... args) + auto converter = [&func](in_out> layer_tracker, + const char* const fmt, + Args&&... args) { - depth_tracker->update_depth(fmt); - + layer_tracker->update_layer(fmt, std::forward(args)...); if constexpr (sizeof...(args) >= 3) { auto as_tuple = std::tuple{std::forward(args)...}; - func(FieldEntry{/*field_type_name*/ std::get<1>(as_tuple), - /*field_name*/ std::get<2>(as_tuple), - depth_tracker->depth()}); + const std::string_view field_type_name = std::get<1>(as_tuple); + const std::string_view field_name = std::get<2>(as_tuple); + FieldAsString field_as_string{.type_name = field_type_name, .name = field_name}; + func(FieldEntry{field_as_string, + layer_tracker->current_enclosing_field(), + layer_tracker->current_providing_base_class()}); + layer_tracker->update_layer_that_is_also_an_entry(fmt, field_as_string); } }; - DepthTracker depth_tracker{}; - __builtin_dump_struct(&instance, converter, in_out{depth_tracker}); - assert(depth_tracker.is_null_depth()); + LayerTracker<> layer_tracker{}; + __builtin_dump_struct(&instance, converter, in_out{layer_tracker}); + assert(layer_tracker.is_null_layer()); } -template +enum class RecursionType +{ + NON_RECURSIVE, + RECURSIVE, +}; + +template constexpr std::size_t field_count_of(const T& instance) { std::size_t counter = 0; for_each_field_entry(instance, [&counter](const FieldEntry& field_entry) { - if (field_entry.depth() == 0) + if (RECURSION_TYPE == RecursionType::RECURSIVE or + field_entry.enclosing_field_name().empty()) { ++counter; } @@ -105,21 +201,22 @@ constexpr std::size_t field_count_of(const T& instance) return counter; } -template +template requires(Reflectable>) constexpr std::size_t field_count_of() { - return field_count_of>({}); + return field_count_of>({}); } -template +template constexpr auto field_info_of(const T& instance) -> FixedVector { FixedVector output{}; for_each_field_entry(instance, [&output](const FieldEntry& field_entry) { - if (field_entry.depth() == 0) + if (RECURSION_TYPE == RecursionType::RECURSIVE or + field_entry.enclosing_field_name().empty()) { output.push_back(field_entry); } @@ -127,12 +224,12 @@ constexpr auto field_info_of(const T& instance) -> FixedVector +template requires(Reflectable>) constexpr auto field_info_of() { - constexpr std::size_t FIELD_COUNT = field_count_of>(); - return field_info_of>(std::decay_t{}); + constexpr std::size_t FIELD_COUNT = field_count_of>(); + return field_info_of>(std::decay_t{}); } } // namespace fixed_containers::reflection_detail diff --git a/test/reflection_test.cpp b/test/reflection_test.cpp index 8bae464e..4c0ae76e 100644 --- a/test/reflection_test.cpp +++ b/test/reflection_test.cpp @@ -21,6 +21,25 @@ struct ChildStruct : public BaseStruct int d; }; +/* + * Output of `__builtin_dump_struct(&instance, printf)` is: +fixed_containers::(anonymous namespace)::MyColors { +int yellow = 0 +double red = 0.000000 +BaseStruct green = { + int a = 0 + int b = 0 +} +ChildStruct purple = { + fixed_containers::(anonymous namespace)::BaseStruct { + int a = 0 + int b = 0 + } + int c = 0 + int d = 0 +} +} + */ struct MyColors { int yellow; @@ -42,51 +61,192 @@ struct NonConstexprDefaultConstructibleWithFields } }; +constexpr std::string_view pick_compiler_specific_string([[maybe_unused]] const std::string_view s1, + [[maybe_unused]] const std::string_view s2) +{ +#if defined(__clang__) && __clang_major__ == 15 + return s1; +#else + return s2; +#endif +} + } // namespace +TEST(Reflection, DebuggingHelper) +{ + using enum reflection_detail::RecursionType; + auto foo = reflection_detail::field_info_of(); + // std::cout << foo.size() << std::endl; + (void)foo; +} + TEST(Reflection, Example) { - static_assert(consteval_compare::equal<4, reflection_detail::field_count_of()>); + using enum reflection_detail::RecursionType; + + static_assert( + consteval_compare::equal<4, reflection_detail::field_count_of()>); - constexpr auto FIELD_INFO = reflection_detail::field_info_of(); + constexpr auto FIELD_INFO = reflection_detail::field_info_of(); static_assert(FIELD_INFO.at(0).field_type_name() == "int"); static_assert(FIELD_INFO.at(0).field_name() == "yellow"); + static_assert(FIELD_INFO.at(0).enclosing_field_type_name() == + "fixed_containers::(anonymous namespace)::MyColors"); + static_assert(FIELD_INFO.at(0).enclosing_field_name() == ""); + static_assert(FIELD_INFO.at(0).providing_base_class_name() == ""); static_assert(FIELD_INFO.at(1).field_type_name() == "double[17]"); static_assert(FIELD_INFO.at(1).field_name() == "red"); + static_assert(FIELD_INFO.at(1).enclosing_field_type_name() == + "fixed_containers::(anonymous namespace)::MyColors"); + static_assert(FIELD_INFO.at(1).enclosing_field_name() == ""); + static_assert(FIELD_INFO.at(1).providing_base_class_name() == ""); -#if defined(__clang__) && __clang_major__ == 15 static_assert(FIELD_INFO.at(2).field_type_name() == - "fixed_containers::(anonymous namespace)::BaseStruct"); -#else - static_assert(FIELD_INFO.at(2).field_type_name() == "BaseStruct"); -#endif + pick_compiler_specific_string( + "fixed_containers::(anonymous namespace)::BaseStruct", "BaseStruct")); static_assert(FIELD_INFO.at(2).field_name() == "green"); + static_assert(FIELD_INFO.at(2).enclosing_field_type_name() == + "fixed_containers::(anonymous namespace)::MyColors"); + static_assert(FIELD_INFO.at(2).enclosing_field_name() == ""); + static_assert(FIELD_INFO.at(2).providing_base_class_name() == ""); -#if defined(__clang__) && __clang_major__ == 15 static_assert(FIELD_INFO.at(3).field_type_name() == - "fixed_containers::(anonymous namespace)::ChildStruct"); -#else - static_assert(FIELD_INFO.at(3).field_type_name() == "ChildStruct"); -#endif + pick_compiler_specific_string( + "fixed_containers::(anonymous namespace)::ChildStruct", "ChildStruct")); static_assert(FIELD_INFO.at(3).field_name() == "purple"); + static_assert(FIELD_INFO.at(3).enclosing_field_type_name() == + "fixed_containers::(anonymous namespace)::MyColors"); + static_assert(FIELD_INFO.at(3).enclosing_field_name() == ""); + static_assert(FIELD_INFO.at(3).providing_base_class_name() == ""); +} + +TEST(Reflection, RecursiveExample) +{ + using enum reflection_detail::RecursionType; + + static_assert( + consteval_compare::equal<10, reflection_detail::field_count_of()>); + + constexpr auto FIELD_INFO = reflection_detail::field_info_of(); + + static_assert(FIELD_INFO.at(0).field_type_name() == "int"); + static_assert(FIELD_INFO.at(0).field_name() == "yellow"); + static_assert(FIELD_INFO.at(0).enclosing_field_type_name() == + "fixed_containers::(anonymous namespace)::MyColors"); + static_assert(FIELD_INFO.at(0).enclosing_field_name() == ""); + static_assert(FIELD_INFO.at(0).providing_base_class_name() == ""); + + static_assert(FIELD_INFO.at(1).field_type_name() == "double[17]"); + static_assert(FIELD_INFO.at(1).field_name() == "red"); + static_assert(FIELD_INFO.at(1).enclosing_field_type_name() == + "fixed_containers::(anonymous namespace)::MyColors"); + static_assert(FIELD_INFO.at(1).enclosing_field_name() == ""); + static_assert(FIELD_INFO.at(1).providing_base_class_name() == ""); + + static_assert(FIELD_INFO.at(2).field_type_name() == + pick_compiler_specific_string( + "fixed_containers::(anonymous namespace)::BaseStruct", "BaseStruct")); + static_assert(FIELD_INFO.at(2).field_name() == "green"); + static_assert(FIELD_INFO.at(2).enclosing_field_type_name() == + "fixed_containers::(anonymous namespace)::MyColors"); + static_assert(FIELD_INFO.at(2).enclosing_field_name() == ""); + static_assert(FIELD_INFO.at(2).providing_base_class_name() == ""); + + { + static_assert(FIELD_INFO.at(3).field_type_name() == "int"); + static_assert(FIELD_INFO.at(3).field_name() == "a"); + static_assert(FIELD_INFO.at(3).enclosing_field_type_name() == + pick_compiler_specific_string( + "fixed_containers::(anonymous namespace)::BaseStruct", "BaseStruct")); + static_assert(FIELD_INFO.at(3).enclosing_field_name() == "green"); + static_assert(FIELD_INFO.at(3).providing_base_class_name() == ""); + + static_assert(FIELD_INFO.at(4).field_type_name() == "int"); + static_assert(FIELD_INFO.at(4).field_name() == "b"); + static_assert(FIELD_INFO.at(4).enclosing_field_type_name() == + pick_compiler_specific_string( + "fixed_containers::(anonymous namespace)::BaseStruct", "BaseStruct")); + static_assert(FIELD_INFO.at(4).enclosing_field_name() == "green"); + static_assert(FIELD_INFO.at(4).providing_base_class_name() == ""); + } + + static_assert(FIELD_INFO.at(5).field_type_name() == + pick_compiler_specific_string( + "fixed_containers::(anonymous namespace)::ChildStruct", "ChildStruct")); + static_assert(FIELD_INFO.at(5).field_name() == "purple"); + static_assert(FIELD_INFO.at(5).enclosing_field_type_name() == + "fixed_containers::(anonymous namespace)::MyColors"); + static_assert(FIELD_INFO.at(5).enclosing_field_name() == ""); + static_assert(FIELD_INFO.at(5).providing_base_class_name() == ""); + + { + static_assert(FIELD_INFO.at(6).field_type_name() == "int"); + static_assert(FIELD_INFO.at(6).field_name() == "a"); + static_assert(FIELD_INFO.at(6).enclosing_field_type_name() == + pick_compiler_specific_string( + "fixed_containers::(anonymous namespace)::ChildStruct", "ChildStruct")); + static_assert(FIELD_INFO.at(6).enclosing_field_name() == "purple"); + static_assert(FIELD_INFO.at(6).providing_base_class_name() == + "fixed_containers::(anonymous namespace)::BaseStruct"); + + static_assert(FIELD_INFO.at(7).field_type_name() == "int"); + static_assert(FIELD_INFO.at(7).field_name() == "b"); + static_assert(FIELD_INFO.at(7).enclosing_field_type_name() == + pick_compiler_specific_string( + "fixed_containers::(anonymous namespace)::ChildStruct", "ChildStruct")); + static_assert(FIELD_INFO.at(7).enclosing_field_name() == "purple"); + static_assert(FIELD_INFO.at(7).providing_base_class_name() == + "fixed_containers::(anonymous namespace)::BaseStruct"); + + static_assert(FIELD_INFO.at(8).field_type_name() == "int"); + static_assert(FIELD_INFO.at(8).field_name() == "c"); + static_assert(FIELD_INFO.at(8).enclosing_field_type_name() == + pick_compiler_specific_string( + "fixed_containers::(anonymous namespace)::ChildStruct", "ChildStruct")); + static_assert(FIELD_INFO.at(8).enclosing_field_name() == "purple"); + static_assert(FIELD_INFO.at(8).providing_base_class_name() == ""); + + static_assert(FIELD_INFO.at(9).field_type_name() == "int"); + static_assert(FIELD_INFO.at(9).field_name() == "d"); + static_assert(FIELD_INFO.at(9).enclosing_field_type_name() == + pick_compiler_specific_string( + "fixed_containers::(anonymous namespace)::ChildStruct", "ChildStruct")); + static_assert(FIELD_INFO.at(9).enclosing_field_name() == "purple"); + static_assert(FIELD_INFO.at(9).providing_base_class_name() == ""); + } } TEST(Reflection, NonConstexprDefaultConstructible) { + using enum reflection_detail::RecursionType; + constexpr NonConstexprDefaultConstructibleWithFields INSTANCE{3, 5.0}; - static_assert(consteval_compare::equal<2, reflection_detail::field_count_of(INSTANCE)>); + static_assert( + consteval_compare::equal<2, reflection_detail::field_count_of(INSTANCE)>); - constexpr auto FIELD_INFO = - reflection_detail::field_info_of(INSTANCE); + constexpr auto FIELD_INFO = reflection_detail:: + field_info_of(INSTANCE)>( + INSTANCE); static_assert(FIELD_INFO.at(0).field_type_name() == "int"); static_assert(FIELD_INFO.at(0).field_name() == "a"); + static_assert( + FIELD_INFO.at(0).enclosing_field_type_name() == + "fixed_containers::(anonymous namespace)::NonConstexprDefaultConstructibleWithFields"); + static_assert(FIELD_INFO.at(0).enclosing_field_name() == ""); + static_assert(FIELD_INFO.at(0).providing_base_class_name() == ""); static_assert(FIELD_INFO.at(1).field_type_name() == "double"); static_assert(FIELD_INFO.at(1).field_name() == "b"); + static_assert( + FIELD_INFO.at(1).enclosing_field_type_name() == + "fixed_containers::(anonymous namespace)::NonConstexprDefaultConstructibleWithFields"); + static_assert(FIELD_INFO.at(1).enclosing_field_name() == ""); + static_assert(FIELD_INFO.at(1).providing_base_class_name() == ""); } } // namespace fixed_containers