Created
January 31, 2023 08:48
-
-
Save yszheda/caabfa1e3d0f8eec02a4b8c5b60cf4da to your computer and use it in GitHub Desktop.
c++14 simple reflection demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <utility> | |
#include <sstream> | |
#include <iostream> | |
#include <iterator> | |
#include <type_traits> | |
// Adopt std::void_t from c++17 | |
template <typename... Ts> | |
struct make_void | |
{ | |
typedef void type; | |
}; | |
template <typename... Ts> | |
using void_t = typename make_void<Ts...>::type; | |
#define VARIADIC_SIZE(...) std::tuple_size<decltype(std::make_tuple<__VA_ARGS__>)>::value | |
#define REM(...) __VA_ARGS__ | |
#define EAT(...) | |
// Strip off the type | |
#define STRIP(x) EAT x | |
// Show the type without parenthesis | |
#define PAIR(x) REM x | |
#define STRINGIZE_IMPL(x) #x | |
#define STRINGIZE(x) STRINGIZE_IMPL(x) | |
#define CONCAT_IMPL(x, y) x##y | |
#define CONCAT(x, y) CONCAT_IMPL(x, y) | |
#define DEFINE_FIELD_DATA(i, arg) \ | |
template <typename T> \ | |
struct field_data<T, i> { \ | |
T& obj; \ | |
field_data(T& obj): obj(obj) {} \ | |
auto value() -> decltype(auto) { \ | |
return (obj.STRIP(arg)); \ | |
} \ | |
static constexpr const char* name() { \ | |
return STRINGIZE(STRIP(arg)); \ | |
} \ | |
}; | |
/** | |
* \def REFLECT_FIELD(i, arg) | |
* define \a i th reflectable field in the class with \a arg of the format "(<type>) <name>" | |
* e.g. REFLECT_FIELD(0, (uint32_t) index) | |
*/ | |
#define REFLECT_FIELD(i, arg) \ | |
PAIR(arg); \ | |
DEFINE_FIELD_DATA(i, arg) | |
/** | |
* \def REFLECT_INITIALIZED_FIELD(i, arg, val) | |
* define \a i th reflectable field in the class with \a arg of the format "(<type>) <name>" and the initialized \a value | |
* e.g. REFLECT_INITIALIZED_FIELD(0, (uint32_t) index, 0) | |
*/ | |
#define REFLECT_INITIALIZED_FIELD(i, arg, val) \ | |
PAIR(arg) = val; \ | |
DEFINE_FIELD_DATA(i, arg) | |
// Check whether a class T is reflectable by checking whether T::field_num is in class T | |
namespace reflectable_impl | |
{ | |
template <class T, typename = void> | |
struct test_reflectable_impl | |
{ | |
template <typename U = test_reflectable_impl> | |
static std::false_type test(size_t); | |
}; | |
struct field_num_struct | |
{ | |
typedef size_t field_num; | |
}; | |
template <class T> | |
struct test_reflectable_impl<T, typename std::enable_if<std::is_class<T>::value>::type> : T, field_num_struct | |
{ | |
template <typename U = test_reflectable_impl, typename = typename U::field_num> | |
static std::false_type test(size_t); | |
static std::true_type test(float); | |
}; | |
template <> | |
struct test_reflectable_impl<std::string, void> | |
{ | |
template <typename U = test_reflectable_impl> | |
static std::false_type test(size_t); | |
}; | |
} | |
template <typename T> | |
using is_reflectable = std::integral_constant<bool, decltype(reflectable_impl::test_reflectable_impl<std::decay_t<T>>::test(0)){}>; | |
#if 0 | |
// NOTE: not work if T::field_num is not public | |
template <typename T, typename = void> | |
struct is_reflectable : std::false_type | |
{}; | |
template <typename T> | |
struct is_reflectable<T, void_t<decltype(std::decay_t<T>::field_num)>> : std::true_type | |
{}; | |
#endif | |
struct reflector | |
{ | |
// Get field_data at index N | |
template <typename T, size_t N> | |
static typename T::template field_data<T, N> get_field_data(T& x) | |
{ | |
return typename T::template field_data<T, N>(x); | |
} | |
// Get the number of fields | |
template <typename T, typename = typename std::enable_if<is_reflectable<T>::value>::type> | |
static constexpr size_t get_field_num() | |
{ | |
return T::field_num; | |
} | |
}; | |
/** | |
* \def REFLECTABLE(num) | |
* enable a class with \a num of reflectable fields | |
* \note must be used before the macros REFLECT_FIELD and REFLECT_INITIALIZED_FIELD | |
*/ | |
#define REFLECTABLE(FIELD_NUM) \ | |
template <typename, size_t> struct field_data; \ | |
static constexpr size_t field_num = FIELD_NUM; \ | |
friend struct reflector; | |
template <typename T, typename F, size_t... N> | |
inline constexpr void reflectable_field_visitor(T&& obj, F&& f, std::index_sequence<N...>) | |
{ | |
using decay_type = std::decay_t<T>; | |
// NOTE: fold expression only supported since C++17 | |
// (void(f(reflector::get_field_data<decay_type, N>(obj).name(), | |
// reflector::get_field_data<decay_type, N>(obj).value())), ...); | |
static_cast<void>(std::initializer_list<int>{(f(reflector::get_field_data<decay_type, N>(obj).name(), | |
reflector::get_field_data<decay_type, N>(obj).value()), 0)...}); | |
} | |
template <typename T, typename F> | |
inline constexpr void field_visitor_impl(T&& obj, F&& f, std::true_type) | |
{ | |
reflectable_field_visitor(std::forward<T>(obj), std::forward<F>(f), std::make_index_sequence<reflector::get_field_num<std::decay_t<T>>()>{}); | |
} | |
template <typename T, typename F> | |
inline constexpr void field_visitor_impl(T&& obj, F&& f, std::false_type) | |
{ | |
} | |
template <typename T, typename F> | |
inline constexpr void field_visitor(T&& obj, F&& f) | |
{ | |
field_visitor_impl(std::forward<T>(obj), std::forward<F>(f), is_reflectable<T>{}); | |
} | |
template <typename T> | |
void dump_obj_impl(std::stringstream& ss, T&& obj, std::false_type, const char* field_name = "", int depth = 0) | |
{ | |
std::fill_n(std::ostream_iterator<std::string>(ss), depth, " "); | |
ss << field_name << ": " << obj << "," << std::endl; | |
} | |
template <typename T> | |
void dump_obj_impl(std::stringstream& ss, T&& obj, std::true_type, const char* field_name = "", int depth = 0) | |
{ | |
auto indent = [depth, &ss] { | |
std::fill_n(std::ostream_iterator<std::string>(ss), depth, " "); | |
}; | |
indent(); | |
ss << field_name << (*field_name ? ": {" : "{") << std::endl; | |
field_visitor(obj, [depth, &ss](auto&& field_name, auto&& value) { | |
dump_obj_impl(ss, value, is_reflectable<decltype(value)>{}, field_name, depth + 1); | |
}); | |
indent(); | |
ss << "}" << (depth == 0 ? "" : ",") << std::endl; | |
} | |
/** | |
* \fn dump_obj | |
* \brief dump obj if it's primitive type or a reflectable class. | |
* \param[in] obj | |
* \param[in] field_name | |
* \param[in] depth | |
*/ | |
// template <typename T, typename = typename std::enable_if<reflector::is_reflectable<T>::value || !std::is_class<std::decay_t<T>>::value>::type> | |
template <typename T> | |
void dump_obj(T&& obj, const char* field_name = "", int depth = 0) | |
{ | |
std::stringstream ss; | |
dump_obj_impl(ss, std::forward<T>(obj), is_reflectable<T>{}, field_name, depth); | |
std::cout << ss.str(); | |
} | |
// Test | |
struct A | |
{ | |
public: | |
REFLECTABLE(2) | |
REFLECT_FIELD(0, (int) x) | |
REFLECT_FIELD(1, (int) y) | |
}; | |
class B | |
{ | |
private: | |
REFLECTABLE(3) | |
REFLECT_INITIALIZED_FIELD(0, (double) x, 1.0) | |
REFLECT_INITIALIZED_FIELD(1, (double) y, 1.0) | |
REFLECT_FIELD(2, (A) a) | |
}; | |
int main() | |
{ | |
A a; | |
B b; | |
dump_obj(a); | |
dump_obj(b); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment