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 21, 2023
1 parent 791ecc5 commit c0a4493
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 0 deletions.
24 changes: 24 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,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 @@ -626,6 +638,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 @@ -76,6 +77,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 +172,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
97 changes: 97 additions & 0 deletions include/fixed_containers/reflection.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#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 <typename 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 <typename T>
requires(ConstexprDefaultConstructible<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>(FixedVector<FieldInfo<>, MAXIMUM_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<>, MAXIMUM_FIELD_COUNT> out{};
__builtin_dump_struct(&instance, converter, out);
return out;
}

template <typename T>
requires(ConstexprDefaultConstructible<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
79 changes: 79 additions & 0 deletions test/reflection_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#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;
};

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");
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");
}

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

0 comments on commit c0a4493

Please sign in to comment.