Skip to content

Commit

Permalink
feat: add more reflect APIs
Browse files Browse the repository at this point in the history
1. add `index_of`, can get member index via its name.
2. add `type_of_member`, can get member type via its name.
3. add `member_of`, can get reference to member via its index or name.
  • Loading branch information
NichtsHsu committed Feb 20, 2024
1 parent 03c003c commit bb4fa77
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 2 deletions.
18 changes: 18 additions & 0 deletions include/conststr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,15 +189,33 @@
* };
*
* int main() {
* // Get member type of `MyStruct` via index
* reflect::type_of<MyStruct, 0> a = 1; // type of `a` is `int`
* reflect::type_of<MyStruct, 1> b = 1.f; // type of `b` is `double`
* reflect::type_of<MyStruct, 2> c = "hello"; // type of `c` is `std::string`
*
* // Also you can get member type via its name
* reflect::type_of_member<MyStruct, "number"> d = 1;
* reflect::type_of_member<MyStruct, "decimal"> e = 1.f;
* reflect::type_of_member<MyStruct, "name"> f = "hello";
*
* // Get member name via index
* std::cout << reflect::number_of_members<MyStruct> << std::endl; // 3 members in `MyStruct`
* std::cout << reflect::name_of<MyStruct, 0> << std::endl; // first member is "number"
* std::cout << reflect::name_of<MyStruct, 1> << std::endl; // second member is "decimal"
* std::cout << reflect::name_of<MyStruct, 2> << std::endl; // third member is "name"
*
* // Get member reference via index ...
* MyStruct s;
* decltype(auto) numref = reflect::member_of<0>(s);
* numref = 100;
* std::cout << s.number << std::endl;
*
* // ...or, via its name
* decltype(auto) nameref = reflect::member_of<"name">(s);
* nameref += "hello world";
* std::cout << s.name << std::endl;
*
* return 0;
* }
* @endcode
Expand Down
106 changes: 104 additions & 2 deletions include/reflect.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1771,8 +1771,8 @@ consteval auto cptr_of_member() {
* @note
* When using MSVC, will return a string in the form of
* `"auto __cdecl reflect::pretty_name<...>(void)"`.
* @tparam Ptr pointer to the the variable or field you want to reflect
* @return A compile-time string containing the name of the variable or field
* @tparam Ptr pointer to the the variable or member you want to reflect
* @return A compile-time string containing the name of the variable or member
* and of type `conststr::cstr`.
* @see conststr::cstr
* @see name_of()
Expand Down Expand Up @@ -1866,6 +1866,108 @@ constexpr auto name_of_ptr = name_of_ptr_impl<Ptr>();
template <typename T, std::size_t N>
constexpr auto name_of = name_of_ptr<cptr_of_member<T, N>()>;
#endif

/**
* @brief Internal implementation of `index_of`.
* Get the index of the member by its name.
* @tparam T any default-constructible aggregate type
* @tparam Name name of the member to search
* @tparam SrchIdx for recursion only, keep it 0
* @return Index of the member.
* @see index_of
*/
template <typename T, conststr::cstr Name, std::size_t SrchIdx>
constexpr size_t index_of_impl()
requires(number_of_members<T> > SrchIdx)
{
if constexpr (Name == name_of<T, SrchIdx>)
return SrchIdx;
else
return index_of_impl<T, Name, SrchIdx + 1>();
}

/**
* @brief Get the index of the member by its name.
* @details
* For example, `reflect::index_of<S, "point">` if the type `S` has a member named `point`.
* @tparam T any default-constructible aggregate type
* @tparam Name name of the member to search
* @return Index of the member.
*/
template <typename T, conststr::cstr Name>
constexpr size_t index_of = index_of_impl<T, Name, 0>();

/**
* @brief Get the type of the member by its name.
* @details
* This is just a simplified API of `reflect::type_of<T, reflect::index_of<T, Name>>`.
* @tparam T any default-constructible aggregate type
* @tparam Name name of the member to search
*/
template <typename T, conststr::cstr Name>
using type_of_member = type_of<T, index_of<T, Name>>;

/**
* @brief Get member reference of object `t`.
* @details
* Please use `decltype(auto)` for deducing the return value type.
* For example:
* @code{.cpp}
* struct S {
* int x;
* int y;
* int z;
* };
* S s = {};
* decltype(auto) zref = reflect::member_of<2>(s);
* zref = 10;
* if (s.z == 10)
* std::cout << "Ok" << std::endl;
* @endcode
* @tparam N index of member
* @tparam T DO NOT specify it, let it be automatically deduced
* @param t object of type `T`
* @return L-value reference if `t` is a l-value reference.
* @return R-value reference if `t` is a r-value reference.
*/
template <std::size_t N, typename T>
constexpr decltype(auto) member_of(T &&t) {
if constexpr (std::is_lvalue_reference_v<T>)
return std::get<N>(to_tuple(std::forward<T>(t)));
else
return std::move(std::get<N>(to_tuple(std::forward<T>(t))));
}

/**
* @brief Get member reference of object `t` via member's name.
* @details
* Please use `decltype(auto)` for deducing the return value type.
* For example:
* @code{.cpp}
* struct S {
* int x;
* int y;
* int z;
* };
* S s = {};
* decltype(auto) zref = reflect::member_of<"z">(s);
* zref = 10;
* if (s.z == 10)
* std::cout << "Ok" << std::endl;
* @endcode
* @tparam Name name of member
* @tparam T DO NOT specify it, let it be automatically deduced
* @param t object of type `T`
* @return L-value reference if `t` is a l-value reference.
* @return R-value reference if `t` is a r-value reference.
*/
template <conststr::cstr Name, typename T>
constexpr decltype(auto) member_of(T &&t)
requires std::same_as<typename decltype(Name)::value_type, char>
{
return member_of<index_of<std::remove_cvref_t<T>, Name>, T>(
std::forward<T>(t));
}
} // namespace reflect

#endif
41 changes: 41 additions & 0 deletions tests/test-reflect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,47 @@ int main() {
static_assert(reflect::name_of<MyStruct, 8> == "uncopyable");
static_assert(reflect::name_of<MyStruct, 9> == "ref_wrapper");

static_assert(
std::same_as<reflect::type_of_member<MyStruct, "number">, int>);
static_assert(
std::same_as<reflect::type_of_member<MyStruct, "decimal">, double>);
static_assert(
std::same_as<reflect::type_of_member<MyStruct, "name">, std::string>);
static_assert(std::same_as<reflect::type_of_member<MyStruct, "array">,
std::size_t[16]>);
static_assert(
std::same_as<reflect::type_of_member<MyStruct, "pointer">, void *>);
static_assert(
std::same_as<reflect::type_of_member<MyStruct, "func_pointer">,
int (*)(int)>);
static_assert(
std::same_as<reflect::type_of_member<MyStruct, "member_pointer">,
int helper::*>);
static_assert(
std::same_as<reflect::type_of_member<MyStruct, "member_func_point">,
int (helper::*)(int)>);
static_assert(std::same_as<reflect::type_of_member<MyStruct, "uncopyable">,
std::unique_ptr<int>>);
static_assert(std::same_as<reflect::type_of_member<MyStruct, "ref_wrapper">,
std::reference_wrapper<int>>);

MyStruct s;
decltype(auto) member0 = reflect::member_of<0>(s);
member0 = 114;
decltype(auto) member0_ = reflect::member_of<0>(std::move(s));
static_assert(std::is_lvalue_reference_v<decltype(member0)>);
static_assert(std::is_rvalue_reference_v<decltype(member0_)>);
if (s.number != 114) return 1;
decltype(auto) member3 = reflect::member_of<3>(s);
member3[10] = 514;
if (s.array[10] != 514) return 1;
decltype(auto) member4 = reflect::member_of<"pointer">(s);
decltype(auto) member4_ = reflect::member_of<"pointer">(std::move(s));
static_assert(std::is_lvalue_reference_v<decltype(member4)>);
static_assert(std::is_rvalue_reference_v<decltype(member4_)>);
member4 = (void *)0x1919810;
if (s.pointer != (void *)0x1919810) return 1;

std::cout << __FILE__ ": all tests passed." << std::endl;

return 0;
Expand Down

0 comments on commit bb4fa77

Please sign in to comment.