Skip to content
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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ cc_library(
includes = ["include"],
deps = [
":concepts",
":fixed_stack",
":fixed_vector",
":in_out",
],
Expand Down
183 changes: 140 additions & 43 deletions include/fixed_containers/reflection.hpp
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"

Expand All @@ -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>
Copy link

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.

Copy link
Collaborator Author

@alexkaratarakis alexkaratarakis Aug 7, 2023

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.

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();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use a sentinel value instead of just std::optional?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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 enclosing_field_name() which is empty for the most external layer). Might go for optional.

Also, inconsistency: https://github.com/teslamotors/fixed-containers/pull/58/files#diff-f67f3de59b958f663c7f020e0cdf5d23c952ad56b4abe200caec3c3f60d9ec47R72
The other instance of this uses a hard-coded "".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#61

}
[[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
Loading
Loading