Tried to do my own implementation of reflection(introspection) for using in my next projects.
Is it optimized at compile time? If no, how can I improve it?
This macro is good interface for adapting structures for reflection?
What features can(should) be added to make reflection(introspection) more useful?
Macros:
#include <tuple>
#include <iostream>
#include <string_view>
#include <boost/preprocessor/comparison/equal.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/subseq.hpp>
#include <boost/preprocessor/seq/to_tuple.hpp>
#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/tuple/pop_front.hpp>
#include <boost/preprocessor/variadic/to_seq.hpp>
#include <boost/vmd/is_tuple.hpp>
namespace reflection {
template<typename T>
struct meta {
};
}
namespace {
template<typename T>
constexpr bool is_sorted(const std::initializer_list<T> &il) {
for (auto it = il.begin(); it != il.end() - 1; it++) {
if (*(it + 1) < *it) {
return false;
}
}
return true;
}
}// namespace
#define REFLECTION_ADAPT_STRUCT_ARGS(...) __VA_ARGS__
#define REFLECTION_ADAPT_STRUCT_STRIP_PARENTHESES(X) X
#define REFLECTION_ADAPT_STRUCT_PASS_PARAMETERS(X) REFLECTION_ADAPT_STRUCT_STRIP_PARENTHESES(REFLECTION_ADAPT_STRUCT_ARGS X)
#define REFLECTION_ADAPT_STRUCT_NAME(info) \
BOOST_PP_IF(BOOST_VMD_IS_TUPLE(info), BOOST_PP_IF(BOOST_VMD_IS_TUPLE(BOOST_PP_TUPLE_ELEM(0, info)), BOOST_PP_TUPLE_ELEM(0, BOOST_PP_TUPLE_ELEM(0, info)), BOOST_PP_TUPLE_ELEM(0, info)), info)
#define REFLECTION_ADAPT_STRUCT_BASE(info) \
BOOST_PP_IF(BOOST_VMD_IS_TUPLE(info), BOOST_PP_IF(BOOST_VMD_IS_TUPLE(BOOST_PP_TUPLE_ELEM(0, info)), BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_SIZE(BOOST_PP_TUPLE_ELEM(0, info)), 1), BOOST_PP_TUPLE_ELEM(1, BOOST_PP_TUPLE_ELEM(0, info)), void), void), void)
#define REFLECTION_ADAPT_STRUCT_ATTRIBUTES(info) \
BOOST_PP_IF(BOOST_VMD_IS_TUPLE(info), BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_SIZE(info), 1), BOOST_PP_TUPLE_POP_FRONT(info), ()), ())
#define REFLECTION_ADAPT_STRUCT_FIELDS_TRANSFORM(s, data, element) \
BOOST_PP_CAT(field_, REFLECTION_ADAPT_STRUCT_NAME(element)())
#define REFLECTION_ADAPT_STRUCT_FIELDS(info) \
BOOST_PP_SEQ_TO_TUPLE(BOOST_PP_SEQ_TRANSFORM(REFLECTION_ADAPT_STRUCT_FIELDS_TRANSFORM, _, BOOST_PP_SEQ_SUBSEQ(info, 1, BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(info)))))
#define REFLECTION_ADAPT_STRUCT_FIELD_OFFSETS_TRANSFORM(s, data, element) \
offsetof(data, REFLECTION_ADAPT_STRUCT_NAME(element))
#define REFLECTION_ADAPT_STRUCT_FIELD_OFFSETS(info) \
BOOST_PP_SEQ_TO_TUPLE(BOOST_PP_SEQ_TRANSFORM(REFLECTION_ADAPT_STRUCT_FIELD_OFFSETS_TRANSFORM, REFLECTION_ADAPT_STRUCT_NAME(BOOST_PP_SEQ_ELEM(0, info)), BOOST_PP_SEQ_SUBSEQ(info, 1, BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(info)))))
#define REFLECTION_ADAPT_STRUCT_HEADER(info) \
template<> \
struct reflection::meta<REFLECTION_ADAPT_STRUCT_NAME(info)> { \
static constexpr std::string_view name = BOOST_PP_STRINGIZE(REFLECTION_ADAPT_STRUCT_NAME(info)); \
using type = REFLECTION_ADAPT_STRUCT_NAME(info); \
using base = REFLECTION_ADAPT_STRUCT_BASE(info); \
static_assert(std::is_same_v<base, void> || std::is_base_of_v<base, REFLECTION_ADAPT_STRUCT_NAME(info)>); \
static constexpr auto attributes = std::make_tuple(REFLECTION_ADAPT_STRUCT_PASS_PARAMETERS(REFLECTION_ADAPT_STRUCT_ATTRIBUTES(info)));
#define REFLECTION_ADAPT_STRUCT_FIELD(struct_name, info) \
struct BOOST_PP_CAT(field_, REFLECTION_ADAPT_STRUCT_NAME(info)) { \
static constexpr std::string_view name = BOOST_PP_STRINGIZE(REFLECTION_ADAPT_STRUCT_NAME(info)); \
using type = decltype(struct_name::REFLECTION_ADAPT_STRUCT_NAME(info)); \
static constexpr auto reference = &struct_name::REFLECTION_ADAPT_STRUCT_NAME(info); \
static constexpr auto attributes = std::make_tuple(REFLECTION_ADAPT_STRUCT_PASS_PARAMETERS(REFLECTION_ADAPT_STRUCT_ATTRIBUTES(info))); \
};
#define REFLECTION_ADAPT_STRUCT_FOOTER(info) \
static constexpr auto fields = std::make_tuple REFLECTION_ADAPT_STRUCT_FIELDS(info); \
static_assert(is_sorted({REFLECTION_ADAPT_STRUCT_PASS_PARAMETERS(REFLECTION_ADAPT_STRUCT_FIELD_OFFSETS(info))})); \
} \
;
#define REFLECTION_ADAPT_STRUCT_I(r, data, index, info) \
BOOST_PP_IF(BOOST_PP_EQUAL(index, 0), REFLECTION_ADAPT_STRUCT_HEADER(info), REFLECTION_ADAPT_STRUCT_FIELD(data, info))
#define REFLECTION_ADAPT_STRUCT(...) \
BOOST_PP_SEQ_FOR_EACH_I(REFLECTION_ADAPT_STRUCT_I, REFLECTION_ADAPT_STRUCT_NAME(BOOST_PP_VARIADIC_ELEM_0(__VA_ARGS__)), BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
REFLECTION_ADAPT_STRUCT_FOOTER(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
Reflection:
namespace {
template<typename Tuple, typename F, std::size_t... Indices>
constexpr void for_each_impl(Tuple &&tuple, F &&f, std::index_sequence<Indices...>) noexcept {
(f(std::get<Indices>(std::forward<Tuple>(tuple))), ...);
}
template<typename Tuple, typename F>
constexpr void for_each(Tuple &&tuple, F &&f) noexcept {
const auto N = std::tuple_size<std::remove_reference_t<Tuple>>::value;
for_each_impl(std::forward<Tuple>(tuple), std::forward<F>(f), std::make_index_sequence<N>{});
}
template <typename T, typename Tuple>
struct has_type : std::false_type {};
template <typename T, typename... Tuple>
struct has_type<T, std::tuple<Tuple...>> : std::disjunction<std::is_same<T, Tuple>...> {};
}// namespace
namespace reflection {
template<typename M, typename F>
constexpr void meta_for_each_field(F &&f) noexcept {
for_each(
M::fields, [&f](const auto &field) constexpr noexcept {
f(field);
});
}
template<typename F>
constexpr const auto meta_name = F::name;
template<typename F>
using meta__type = typename F::type;
template<typename F>
constexpr const auto &meta_attributes = F::attributes;
template<typename F, typename T>
constexpr const auto &meta_attribute = std::get<T>(meta_attributes<F>);
template<typename F, typename T>
constexpr auto meta_has_attribute = has_type<T, std::decay_t<decltype(meta_attributes<F>)>>::value;
template<typename F>
constexpr auto &meta_value(auto &&t) noexcept {
return t.*F::reference;
}
}// namespace reflection
Usage:
struct A {
int a;
};
struct B : A {
std::string s;
bool b;
float f;
float f1;
};
struct Attr1 {
std::string_view data;
};
struct Attr2 {
bool data;
};
REFLECTION_ADAPT_STRUCT(
((B, A), Attr1{"test"}),
s,
b,
(f, Attr2{true}),
(f1)
)
int main() {
B b;
b.a = 42;
b.s = "hello";
b.b = true;
b.f = 42.42f;
b.f1 = -42;
using meta_B = reflection::meta<B>;
std::cout << reflection::meta_name<meta_B> << std::endl;
if constexpr (reflection::meta_has_attribute<meta_B, Attr1>) {
std::cout << "Attr1 " << reflection::meta_attribute<meta_B, Attr1>.data << std::endl;
}
reflection::meta_for_each_field<meta_B>([&b]<typename F>(F) constexpr {
if constexpr (reflection::meta_has_attribute<F, Attr2>) {
std::cout << "Attr2 " << reflection::meta_attribute<F, Attr2>.data << std::endl;
}
std::cout << reflection::meta_name<F> << " " << reflection::meta_value<F>(b) << std::endl;
});
}
Output:
B
Attr1 test
s hello
b 1
Attr2 1
f 42.42
f1 -42
REFLECTION_ADAPT_STRUCT in this case will expand to
template<>
struct reflection::meta<B> {
static constexpr std::string_view name = "B";
using type = B;
using base = A;
static_assert(std::is_same_v<base, void> || std::is_base_of_v<base, B>);
static constexpr auto attributes = std::make_tuple(Attr1{"test"});
struct field_s {
static constexpr std::string_view name = "s";
using type = decltype(B::s);
static constexpr auto reference = &B::s;
static constexpr auto attributes = std::make_tuple();
};
struct field_b {
static constexpr std::string_view name = "b";
using type = decltype(B::b);
static constexpr auto reference = &B::b;
static constexpr auto attributes = std::make_tuple();
};
struct field_f {
static constexpr std::string_view name = "f";
using type = decltype(B::f);
static constexpr auto reference = &B::f;
static constexpr auto attributes = std::make_tuple(Attr2{true});
};
struct field_f1 {
static constexpr std::string_view name = "f1";
using type = decltype(B::f1);
static constexpr auto reference = &B::f1;
static constexpr auto attributes = std::make_tuple();
};
static constexpr auto fields = std::make_tuple(field_s(), field_b(), field_f(), field_f1());
static_assert(is_sorted({offsetof(B, s), offsetof(B, b), offsetof(B, f), offsetof(B, f1)}));
};