Skip to content

Commit

Permalink
[reflection] Introduce for_each_field()
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkaratarakis committed Jan 19, 2024
1 parent ea430b2 commit fbbcb2f
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 0 deletions.
2 changes: 2 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ cc_library(
":fixed_stack",
":fixed_vector",
":in_out",
":tuples",
],
copts = ["-std=c++20"],
)
Expand Down Expand Up @@ -1087,6 +1088,7 @@ cc_test(
name = "reflection_test",
srcs = ["test/reflection_test.cpp"],
deps = [
":fixed_vector",
":reflection",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
Expand Down
29 changes: 29 additions & 0 deletions include/fixed_containers/reflection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
#include "fixed_containers/fixed_stack.hpp"
#include "fixed_containers/fixed_vector.hpp"
#include "fixed_containers/in_out.hpp"
#include "fixed_containers/tuples.hpp"

#include <array>
#include <concepts>
#include <optional>
#include <string_view>
#include <tuple>
#include <type_traits>

// https://clang.llvm.org/docs/LanguageExtensions.html#builtin-dump-struct
static_assert(__has_builtin(__builtin_dump_struct),
Expand Down Expand Up @@ -234,4 +236,31 @@ constexpr auto field_info_of()
return field_info_of<RECURSION_TYPE, FIELD_COUNT, std::decay_t<T>>(std::decay_t<T>{});
}

namespace caching
{
template <typename T>
requires(Reflectable<std::decay_t<T>>)
inline constexpr auto FIELD_INFO =
field_info_of<fixed_containers::reflection_detail::RecursionType::NON_RECURSIVE, T>();

template <typename T>
requires(Reflectable<std::decay_t<T>>)
inline constexpr auto FIELD_INFO_RECURSIVE_DEPTH_FIRST_ORDER =
field_info_of<fixed_containers::reflection_detail::RecursionType::RECURSIVE_DEPTH_FIRST_ORDER,
T>();
} // namespace caching

template <typename T, typename Func>
requires(Reflectable<std::decay_t<T>>)
constexpr void for_each_field(T&& instance, Func&& func)
{
constexpr const auto& FIELD_INFO = caching::FIELD_INFO<T>;
auto tuple_view = tuples::as_tuple_view<FIELD_INFO.size()>(instance);
tuples::for_each_entry(tuple_view,
[&func]<typename Field>(std::size_t index, Field&& field) {
std::forward<Func>(func)(FIELD_INFO.at(index).field_name(),
std::forward<Field>(field));
});
}

} // namespace fixed_containers::reflection_detail
152 changes: 152 additions & 0 deletions test/reflection_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,64 @@

#include "fixed_containers/reflection.hpp"

#include "fixed_containers/fixed_vector.hpp"

#include <gtest/gtest.h>

#include <cstddef>
#include <string_view>

namespace
{
struct GlobalNamespaceStruct3
{
int a1;
int b2;
int c3;
};

struct GlobalNamespaceStruct8
{
int a1;
int b2;
int c3;
int d4;
double e5;
double f6;
int g7;
int h8;
};

template <class T, class U>
struct TemplatedStructWithTwoArgumentsSoItContainsAComma
{
int a1;
};

struct EmptyStruct
{
};

template <class T, class U>
struct EmptyTemplatedStruct
{
};

} // namespace

namespace fixed_containers
{
namespace
{
struct MySimpleStruct
{
int my_int;
double my_double;
int my_int2;
const int* my_int_ptr;
int* int_int_ptr2;
};

struct BaseStruct
{
int a;
Expand Down Expand Up @@ -373,6 +425,106 @@ TEST(Reflection, FieldCountLimits)
#endif
}

TEST(Reflection, ForEachField)
{
MySimpleStruct a{};

FixedVector<std::string_view, 10> field_list = [&]()
{
FixedVector<std::string_view, 10> out{};
reflection_detail::for_each_field(a,
[&]<class T>(const std::string_view& name, T& field)
{
if constexpr (std::is_same_v<int, T>)
{
field = 5;
}

out.push_back(name);
});

return out;
}();

EXPECT_EQ(field_list.at(0), "my_int");
EXPECT_EQ(field_list.at(1), "my_double");
EXPECT_EQ(field_list.at(2), "my_int2");
EXPECT_EQ(field_list.at(3), "my_int_ptr");
EXPECT_EQ(field_list.at(4), "int_int_ptr2");

EXPECT_EQ(a.my_int, 5);

constexpr GlobalNamespaceStruct8 b{};

field_list = [&]()
{
FixedVector<std::string_view, 10> out{};
reflection_detail::for_each_field(
b, [&]<class T>(const std::string_view& name, const T&) { out.push_back(name); });
return out;
}();

EXPECT_EQ(field_list.at(0), "a1");
EXPECT_EQ(field_list.at(1), "b2");
EXPECT_EQ(field_list.at(2), "c3");
EXPECT_EQ(field_list.at(3), "d4");
EXPECT_EQ(field_list.at(4), "e5");
EXPECT_EQ(field_list.at(5), "f6");
EXPECT_EQ(field_list.at(6), "g7");
EXPECT_EQ(field_list.at(7), "h8");

GlobalNamespaceStruct3 c{};
field_list = [&]()
{
FixedVector<std::string_view, 10> out{};
reflection_detail::for_each_field(
c, [&]<class T>(const std::string_view& name, const T&) { out.push_back(name); });
return out;
}();
EXPECT_EQ(field_list.at(0), "a1");
EXPECT_EQ(field_list.at(1), "b2");
EXPECT_EQ(field_list.at(2), "c3");

TemplatedStructWithTwoArgumentsSoItContainsAComma<int, double> d{};
field_list = [&]()
{
FixedVector<std::string_view, 10> out{};
reflection_detail::for_each_field(
d, [&]<class T>(const std::string_view& name, const T&) { out.push_back(name); });
return out;
}();
EXPECT_EQ(field_list.at(0), "a1");
}

TEST(Reflection, ForEachField_EmptyStruct)
{
{
EmptyStruct empty_struct{};
std::size_t counter = 0;
[&]()
{
reflection_detail::for_each_field(
empty_struct,
[&]<class T>(const std::string_view& /*name*/, const T&) { counter++; });
}();

EXPECT_EQ(0, counter);
}

{
EmptyTemplatedStruct<int, double> empty_templated_struct{};
std::size_t counter = 0;
[&]()
{
reflection_detail::for_each_field(
empty_templated_struct,
[&]<class T>(const std::string_view& /*name*/, const T&) { counter++; });
}();

EXPECT_EQ(0, counter);
}
}

} // namespace fixed_containers

#endif
Expand Down

0 comments on commit fbbcb2f

Please sign in to comment.