3
\$\begingroup\$

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?

Godbolt

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)}));
};
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.