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 11, 2023
1 parent d7dea57 commit 5dc9f5a
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 0 deletions.
24 changes: 24 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,18 @@ cc_library(
copts = ["-std=c++20"],
)

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

cc_library(
name = "source_location",
hdrs = ["include/fixed_containers/source_location.hpp"],
Expand Down Expand Up @@ -590,6 +602,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
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ cmake_minimum_required(VERSION 3.16)

set(CMAKE_CXX_STANDARD 20)
# strongly encouraged to enable this globally to avoid conflicts between

# -Wpedantic being enabled and -std=c++20 and -std=gnu++20 for example
# when compiling with PCH enabled
set(CMAKE_CXX_EXTENSIONS ON)
Expand Down Expand Up @@ -75,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 @@ -165,6 +167,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
93 changes: 93 additions & 0 deletions include/fixed_containers/reflection.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#pragma once

#include "fixed_containers/concepts.hpp"
#include "fixed_containers/fixed_string.hpp"
#include "fixed_containers/fixed_vector.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 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 <ConstexprDefaultConstructible T>
constexpr std::size_t field_count_of(const T& instance = {})
{
auto converter =
[]<typename... Args>(std::size_t& out, const char* const /*fmt*/, Args&&... args)
{
if constexpr (sizeof...(args) >= 3)
{
++out;
}
};

std::size_t counter = 0;
__builtin_dump_struct(&instance, converter, counter);
return counter;
}

template <ConstexprDefaultConstructible T>
constexpr auto field_info_of(const T& instance = {}) -> std::array<FieldInfo<>, field_count_of<T>()>
{
constexpr std::size_t FIELD_COUNT = field_count_of<T>();

auto converter = []<typename... Args>(FixedVector<FieldInfo<>, FIELD_COUNT>& out,
const char* const /*fmt*/,
Args&&... args)
{
if constexpr (sizeof...(args) >= 3)
{
auto as_tuple = std::tuple{std::forward<Args>(args)...};
out.emplace_back(/*field_type_name*/ std::get<1>(as_tuple),
/*field_name*/ std::get<2>(as_tuple));
}
};

FixedVector<FieldInfo<>, FIELD_COUNT> out_vec{};
__builtin_dump_struct(&instance, converter, out_vec);

assert(out_vec.size() == FIELD_COUNT);

std::array<FieldInfo<>, FIELD_COUNT> out{};
for (std::size_t i = 0; i < FIELD_COUNT; i++)
{
out.at(i) = out_vec.at(i);
}

return out;
}

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

#include "fixed_containers/reflection.hpp"

#include <gtest/gtest.h>

namespace fixed_containers
{
namespace
{
struct BaseStruct
{
};

struct ChildStruct : public BaseStruct
{
};

struct MyColors
{
int yellow;
double red;
BaseStruct green;
ChildStruct purple;
};
} // namespace

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

constexpr std::array 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");
static_assert(FIELD_INFO.at(1).field_name() == "red");

static_assert(FIELD_INFO.at(2).field_type_name() == "BaseStruct");
static_assert(FIELD_INFO.at(2).field_name() == "green");

static_assert(FIELD_INFO.at(3).field_type_name() == "ChildStruct");
static_assert(FIELD_INFO.at(3).field_name() == "purple");
}

} // namespace fixed_containers

#endif

0 comments on commit 5dc9f5a

Please sign in to comment.