-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recursive reflection #58
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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,120 +20,216 @@ namespace fixed_containers::reflection_detail | |
template <typename T> | ||
concept Reflectable = std::is_class_v<T> && ConstexprDefaultConstructible<T>; | ||
|
||
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 <std::size_t MAXIMUM_LAYERS = 32> | ||
class LayerTracker | ||
{ | ||
FixedStack<LayerType, MAXIMUM_LAYERS> layer_type_stack_; | ||
FixedStack<FieldAsString, MAXIMUM_LAYERS> nesting_stack_; | ||
FixedStack<std::string_view, MAXIMUM_LAYERS> 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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why use a sentinel value instead of just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went back and forth on this (and also for the same issue for Also, inconsistency: https://github.com/teslamotors/fixed-containers/pull/58/files#diff-f67f3de59b958f663c7f020e0cdf5d23c952ad56b4abe200caec3c3f60d9ec47R72 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
[[nodiscard]] constexpr bool is_null_depth() const { return depth_ == -1; } | ||
|
||
constexpr void update_depth(const std::string_view fmt) | ||
template <typename... Args> | ||
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>(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>(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); | ||
} | ||
} | ||
}; | ||
|
||
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 <typename T, std::invocable<FieldEntry> Func> | ||
constexpr void for_each_field_entry(const T& instance, Func func) | ||
{ | ||
auto converter = [&func]<typename... Args>( | ||
in_out<DepthTracker> depth_tracker, const char* const fmt, Args&&... args) | ||
auto converter = [&func]<typename... Args>(in_out<LayerTracker<32>> layer_tracker, | ||
const char* const fmt, | ||
Args&&... args) | ||
{ | ||
depth_tracker->update_depth(fmt); | ||
|
||
layer_tracker->update_layer(fmt, std::forward<Args>(args)...); | ||
if constexpr (sizeof...(args) >= 3) | ||
{ | ||
auto as_tuple = std::tuple{std::forward<Args>(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 <typename T> | ||
enum class RecursionType | ||
{ | ||
NON_RECURSIVE, | ||
RECURSIVE, | ||
}; | ||
|
||
template <RecursionType RECURSION_TYPE, typename T> | ||
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; | ||
} | ||
}); | ||
return counter; | ||
} | ||
|
||
template <typename T> | ||
template <RecursionType RECURSION_TYPE, typename T> | ||
requires(Reflectable<std::decay_t<T>>) | ||
constexpr std::size_t field_count_of() | ||
{ | ||
return field_count_of<std::decay_t<T>>({}); | ||
return field_count_of<RECURSION_TYPE, std::decay_t<T>>({}); | ||
} | ||
|
||
template <std::size_t MAXIMUM_FIELD_COUNT = 16, typename T> | ||
template <RecursionType RECURSION_TYPE, std::size_t MAXIMUM_FIELD_COUNT = 16, typename T> | ||
constexpr auto field_info_of(const T& instance) -> FixedVector<FieldEntry, MAXIMUM_FIELD_COUNT> | ||
{ | ||
FixedVector<FieldEntry, MAXIMUM_FIELD_COUNT> 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); | ||
} | ||
}); | ||
return output; | ||
} | ||
|
||
template <typename T> | ||
template <RecursionType RECURSION_TYPE, typename T> | ||
requires(Reflectable<std::decay_t<T>>) | ||
constexpr auto field_info_of() | ||
{ | ||
constexpr std::size_t FIELD_COUNT = field_count_of<std::decay_t<T>>(); | ||
return field_info_of<FIELD_COUNT, std::decay_t<T>>(std::decay_t<T>{}); | ||
constexpr std::size_t FIELD_COUNT = field_count_of<RECURSION_TYPE, std::decay_t<T>>(); | ||
return field_info_of<RECURSION_TYPE, FIELD_COUNT, std::decay_t<T>>(std::decay_t<T>{}); | ||
} | ||
|
||
} // namespace fixed_containers::reflection_detail |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make it so that this template parameter can be provided by the client? For example, perhaps we can make
RecursionType
a variant that supports passing in recursion depth if the client wants recursive reflection info.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this was intended to be exposed. Let me consider a nice way to do this, as this type is not meant to be used by the user as it stands.