From 5dc9f5a478bb20e57af92a76246c39f4af7375ac Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Fri, 20 Jan 2023 15:56:09 -0800 Subject: [PATCH] Introduce reflection.hpp --- BUILD.bazel | 24 +++++++ CMakeLists.txt | 4 ++ include/fixed_containers/reflection.hpp | 93 +++++++++++++++++++++++++ test/reflection_test.cpp | 49 +++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 include/fixed_containers/reflection.hpp create mode 100644 test/reflection_test.cpp diff --git a/BUILD.bazel b/BUILD.bazel index e7418098..d850a932 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -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"], @@ -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"], diff --git a/CMakeLists.txt b/CMakeLists.txt index 841e22c8..59fac174 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -75,6 +76,7 @@ if(${USING_CLANG}) elseif(MSVC) SET(DIAGNOSTIC_FLAGS ${DIAGNOSTIC_FLAGS} /W4 + /wd4067 ) else() SET(DIAGNOSTIC_FLAGS ${DIAGNOSTIC_FLAGS} @@ -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) diff --git a/include/fixed_containers/reflection.hpp b/include/fixed_containers/reflection.hpp new file mode 100644 index 00000000..b5161d9b --- /dev/null +++ b/include/fixed_containers/reflection.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include "fixed_containers/concepts.hpp" +#include "fixed_containers/fixed_string.hpp" +#include "fixed_containers/fixed_vector.hpp" + +#include +#include +#include +#include + +// 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 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 +constexpr std::size_t field_count_of(const T& instance = {}) +{ + auto converter = + [](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 +constexpr auto field_info_of(const T& instance = {}) -> std::array, field_count_of()> +{ + constexpr std::size_t FIELD_COUNT = field_count_of(); + + auto converter = [](FixedVector, FIELD_COUNT>& out, + const char* const /*fmt*/, + Args&&... args) + { + if constexpr (sizeof...(args) >= 3) + { + auto as_tuple = std::tuple{std::forward(args)...}; + out.emplace_back(/*field_type_name*/ std::get<1>(as_tuple), + /*field_name*/ std::get<2>(as_tuple)); + } + }; + + FixedVector, FIELD_COUNT> out_vec{}; + __builtin_dump_struct(&instance, converter, out_vec); + + assert(out_vec.size() == FIELD_COUNT); + + std::array, 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 diff --git a/test/reflection_test.cpp b/test/reflection_test.cpp new file mode 100644 index 00000000..cb3c41bd --- /dev/null +++ b/test/reflection_test.cpp @@ -0,0 +1,49 @@ +#if __has_builtin(__builtin_dump_struct) + +#include "fixed_containers/reflection.hpp" + +#include + +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()>); + + constexpr std::array FIELD_INFO = reflection_detail::field_info_of(); + + 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