Skip to content

Instantly share code, notes, and snippets.

@yszheda
Created January 31, 2023 08:48
Show Gist options
  • Save yszheda/caabfa1e3d0f8eec02a4b8c5b60cf4da to your computer and use it in GitHub Desktop.
Save yszheda/caabfa1e3d0f8eec02a4b8c5b60cf4da to your computer and use it in GitHub Desktop.
c++14 simple reflection demo
#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