Skip to content

Commit

Permalink
Introduce reflection.hpp
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkaratarakis committed Jul 24, 2023
1 parent 791ecc5 commit 5fc824e
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 0 deletions.
25 changes: 25 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,19 @@ cc_library(
copts = ["-std=c++20"],
)

cc_library(
name = "reflection",
hdrs = ["include/fixed_containers/reflection.hpp"],
includes = ["include"],
deps = [
":concepts",
":fixed_string",
":fixed_vector",
":in_out",
],
copts = ["-std=c++20"],
)

cc_library(
name = "source_location",
hdrs = ["include/fixed_containers/source_location.hpp"],
Expand Down Expand Up @@ -626,6 +639,18 @@ cc_test(
copts = ["-std=c++20"],
)

cc_test(
name = "reflection_test",
srcs = ["test/reflection_test.cpp"],
deps = [
":reflection",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
copts = ["-std=c++20"],
)


cc_test(
name = "string_literal_test",
srcs = ["test/string_literal_test.cpp"],
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ if(${USING_CLANG})
elseif(MSVC)
SET(DIAGNOSTIC_FLAGS ${DIAGNOSTIC_FLAGS}
/W4
/wd4067
)
else()
SET(DIAGNOSTIC_FLAGS ${DIAGNOSTIC_FLAGS}
Expand Down Expand Up @@ -170,6 +171,8 @@ if(BUILD_TESTS)
add_test_dependencies(pair_test)
add_executable(pair_view_test test/pair_view_test.cpp)
add_test_dependencies(pair_view_test)
add_executable(reflection_test test/reflection_test.cpp)
add_test_dependencies(reflection_test)
add_executable(string_literal_test test/string_literal_test.cpp)
add_test_dependencies(string_literal_test)
add_executable(type_name_test test/type_name_test.cpp)
Expand Down
142 changes: 142 additions & 0 deletions include/fixed_containers/reflection.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#pragma once

#include "fixed_containers/concepts.hpp"
#include "fixed_containers/fixed_string.hpp"
#include "fixed_containers/fixed_vector.hpp"
#include "fixed_containers/in_out.hpp"

#include <array>
#include <cassert>
#include <string_view>
#include <tuple>

// https://clang.llvm.org/docs/LanguageExtensions.html#builtin-dump-struct
static_assert(__has_builtin(__builtin_dump_struct),
"Static reflection requires __builtin_dump_struct() to be available");

namespace fixed_containers::reflection_detail
{
template <typename T>
concept Reflectable = std::is_class_v<T> && ConstexprDefaultConstructible<T>;

class DepthTracker
{
int depth_{-1};

public:
[[nodiscard]] constexpr bool is_base_depth() const { return depth_ == 0; }
[[nodiscard]] constexpr bool is_null_depth() const { return depth_ == -1; }

constexpr void update_depth(const char* const fmt)
{
std::string_view fmt_as_string_view{fmt};
const char special_char = fmt_as_string_view.at(fmt_as_string_view.size() - 2);
if (special_char == '{')
{
++depth_;
}
if (special_char == '}')
{
--depth_;
}
}
};

template <typename FieldTypeNameStringType = fixed_string_detail::FixedString<128>,
typename FieldNameStringType = fixed_string_detail::FixedString<64>>
class FieldInfo
{
private:
FieldTypeNameStringType field_type_name_{};
FieldNameStringType field_name_{};

public:
constexpr FieldInfo() noexcept
: field_type_name_{}
, field_name_{}
{
}

constexpr FieldInfo(const std::string_view& field_type_name,
const std::string_view& field_name) noexcept
: field_type_name_{field_type_name}
, field_name_{field_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_; }
};

template <typename T>
constexpr std::size_t field_count_of(const T& instance)
{
auto converter = []<typename... Args>(in_out<std::size_t> output,
in_out<DepthTracker> depth_tracker,
const char* const fmt,
Args&&... args)
{
depth_tracker->update_depth(fmt);

if constexpr (sizeof...(args) >= 3)
{
if (depth_tracker->is_base_depth())
{
++(*output);
}
}
};

std::size_t counter = 0;
DepthTracker depth_tracker{};
__builtin_dump_struct(&instance, converter, in_out{counter}, in_out{depth_tracker});
assert(depth_tracker.is_null_depth());
return counter;
}

template <typename T>
requires(Reflectable<std::decay_t<T>>)
constexpr std::size_t field_count_of()
{
return field_count_of<std::decay_t<T>>({});
}

template <std::size_t MAXIMUM_FIELD_COUNT = 16, typename T>
constexpr auto field_info_of(const T& instance) -> FixedVector<FieldInfo<>, MAXIMUM_FIELD_COUNT>
{
auto converter =
[]<typename... Args>(in_out<FixedVector<FieldInfo<>, MAXIMUM_FIELD_COUNT>> output,
in_out<DepthTracker> depth_tracker,
const char* const fmt,
Args&&... args)
{
depth_tracker->update_depth(fmt);

auto as_tuple = std::tuple{std::forward<Args>(args)...};
if constexpr (sizeof...(args) >= 3)
{
if (depth_tracker->is_base_depth())
{
output->emplace_back(/*field_type_name*/ std::get<1>(as_tuple),
/*field_name*/ std::get<2>(as_tuple));
}
}
};

FixedVector<FieldInfo<>, MAXIMUM_FIELD_COUNT> output{};
DepthTracker depth_tracker{};
__builtin_dump_struct(&instance, converter, in_out{output}, in_out{depth_tracker});
assert(depth_tracker.is_null_depth());
return output;
}

template <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>{});
}

} // namespace fixed_containers::reflection_detail
95 changes: 95 additions & 0 deletions test/reflection_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#if __has_builtin(__builtin_dump_struct)
#if defined(__clang__) && __clang_major__ >= 15

#include "fixed_containers/reflection.hpp"

#include <gtest/gtest.h>

namespace fixed_containers
{
namespace
{
struct BaseStruct
{
int a;
int b;
};

struct ChildStruct : public BaseStruct
{
int c;
int d;
};

struct MyColors
{
int yellow;
double red[17];
BaseStruct green;
ChildStruct purple;
};

struct NonConstexprDefaultConstructibleWithFields
{
int a;
double b;

// Needs to be constexpr constructible, just not with a default constructor
constexpr NonConstexprDefaultConstructibleWithFields(int a0, double b0)
: a{a0}
, b{b0}
{
}
};

} // namespace

TEST(Reflection, Example)
{
static_assert(consteval_compare::equal<4, reflection_detail::field_count_of<MyColors>()>);

constexpr auto FIELD_INFO = reflection_detail::field_info_of<MyColors>();

static_assert(FIELD_INFO.at(0).field_type_name() == "int");
static_assert(FIELD_INFO.at(0).field_name() == "yellow");

static_assert(FIELD_INFO.at(1).field_type_name() == "double[17]");
static_assert(FIELD_INFO.at(1).field_name() == "red");

#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
static_assert(FIELD_INFO.at(2).field_name() == "green");

#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
static_assert(FIELD_INFO.at(3).field_name() == "purple");
}

TEST(Reflection, NonConstexprDefaultConstructible)
{
constexpr NonConstexprDefaultConstructibleWithFields INSTANCE{3, 5.0};

static_assert(consteval_compare::equal<2, reflection_detail::field_count_of(INSTANCE)>);

constexpr auto FIELD_INFO =
reflection_detail::field_info_of<reflection_detail::field_count_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(1).field_type_name() == "double");
static_assert(FIELD_INFO.at(1).field_name() == "b");
}

} // namespace fixed_containers

#endif
#endif

0 comments on commit 5fc824e

Please sign in to comment.