veselink1 / refl-cpp

Static reflection for C++17 (compile-time enumeration, attributes, proxies, overloads, template functions, metaprogramming).
https://veselink1.github.io/refl-cpp/md__introduction.html
MIT License
1.06k stars 77 forks source link

Question about tuple things and bindings #7

Closed Milerius closed 4 years ago

Milerius commented 4 years ago

Hello i try to use refl-hpp instead of my own reflection library

For registering into a scripting type system using reflection i was using the following snippet


 template <typename ...Ts>
 using meta::map = std::tuple<Ts...>;

struct transform_2d
    {
        transform_2d() noexcept = default;

        transform_2d(float x_,
                     float y_, float width_, float height_, float scale_x_, float scale_y_,
                     float angle_) noexcept :
            x(x_),
            y(y_),
            width(width_),
            height(height_),
            scale_x(scale_x_),
            scale_y(scale_y_),
            angle(angle_),
            original_x(x_)
        {
        }

        float x{0.0f}; //!< x
        float y{0.0f}; //!< y
        float width{0.0f};
        float height{0.0f};
        float scale_x{1.0f};
        float scale_y{1.0f};
        float angle{0.0f};
        bool rotating{false};

        //! Original (mutable because of EnTT, (should not be modified))
        mutable float original_x{x};
        mutable float original_y{y};
        mutable float original_width{width};
        mutable float original_height{height};

        reflect_class(transform_2d)

        static constexpr auto reflected_functions() noexcept
        {
            return meta::makeMap();
        }

        static constexpr auto reflected_members() noexcept
        {
            return meta::makeMap(reflect_member(&transform_2d::y),
                                 reflect_member(&transform_2d::x),
                                 reflect_member(&transform_2d::width),
                                 reflect_member(&transform_2d::height),
                                 reflect_member(&transform_2d::scale_x),
                                 reflect_member(&transform_2d::scale_y),
                                 reflect_member(&transform_2d::angle),
                                 reflect_member(&transform_2d::rotating));
        }
    };

 template <typename T>
    void register_type(sol::state &state, shiva::logging::logger logger) noexcept
    {
        const auto table = std::tuple_cat(
            std::make_tuple(T::class_name()),
            T::reflected_functions(),
            T::reflected_members());

        try {
            std::apply(
                [&state](auto &&...params) {
                    state.new_usertype<T>(std::forward<decltype(params)>(params)...);
                }, table);
        }
        catch (const std::exception &error) {
            logger->error("error: {}", error.what());
            return;
        }

        logger->info("successfully registering type: {}", T::class_name());
    }

register_type<transform2d>();

It"s was generating a snippet like:

new_usertype<transform_2d>("transform_2d",
        "x", &my_class::x,
        "y", &my_class::y); /

Is it possible using your lib ?

Milerius commented 4 years ago

Now so far i have:

template<typename TypeToRegister>
        void register_type() noexcept
        {
            const auto table = std::tuple_cat(std::make_tuple(refl::reflect<TypeToRegister>().name.str()));
        }

But i dont know how to do for the rest

Milerius commented 4 years ago
namespace antara::gaming::metaprog
{
    template<class U, class T, bool can_move>
    struct wrapper {
        T* ptr;
        wrapper(T& t) : ptr(std::addressof(t)) {}

        using unwrapped_type =
        std::conditional_t<can_move,
                std::conditional_t<std::is_lvalue_reference<U>{}, T&, T&&>,
                std::conditional_t<std::is_rvalue_reference<U>{}, T&&, T&>>;
        using tuple_element_type = U;

        unwrapped_type unwrap() const{
            return std::forward<unwrapped_type>(*ptr);
        }
    };

    template<class... Wrappers, std::size_t... Is>
    auto unwrap_tuple(const std::tuple<Wrappers...>& t, std::index_sequence<Is...>) {
        return std::tuple<typename Wrappers::tuple_element_type...>(std::get<Is>(t).unwrap()...);
    }

    template<class... Wrappers>
    auto unwrap_tuple(const std::tuple<Wrappers...>& t) {
        return unwrap_tuple(t, std::index_sequence_for<Wrappers...>());
    }

    template<bool can_move, class V, class T>
    auto wrap_and_flatten(T& t, char){
        return std::make_tuple(wrapper<V, T, can_move>(t));
    }
    template<class T> struct is_tuple : std::false_type {};
    template<class... Ts> struct is_tuple<std::tuple<Ts...>> : std::true_type {};
    template<class T> struct is_tuple<const T> : is_tuple<T> {};
    template<class T> struct is_tuple<volatile T> : is_tuple<T> {};

    template<bool can_move, class, class Tuple,
            class = std::enable_if_t<is_tuple<std::decay_t<Tuple>>{}>>
    auto wrap_and_flatten(Tuple& t, int);

    template<bool can_move, class Tuple, std::size_t... Is>
    auto wrap_and_flatten(Tuple& t, std::index_sequence<Is...>) {
        return std::tuple_cat(wrap_and_flatten<can_move, std::tuple_element_t<Is, std::remove_cv_t<Tuple>>>(std::get<Is>(t), 0)...);
    }

    template<bool can_move, class V, class Tuple, class>
    auto wrap_and_flatten(Tuple& t, int) {
        using seq_type = std::make_index_sequence<std::tuple_size<Tuple>{}>;
        return wrap_and_flatten<can_move>(t, seq_type());
    }

    template<class Tuple>
    auto wrap_and_flatten_tuple(Tuple&& t){
        constexpr bool can_move = !std::is_lvalue_reference<Tuple>{};
        using seq_type = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>{}>;
        return wrap_and_flatten<can_move>(t, seq_type());
    }

    template <typename T>
    auto merge_tuple(T&& t)
    {
        return unwrap_tuple(wrap_and_flatten_tuple(std::forward<T>(t)));
    }
}

namespace antara::gaming::lua
{
    class scripting_system final : public ecs::logic_update_system<lua::scripting_system>
    {
    public:
        scripting_system(entt::registry &entity_registry, entt::dispatcher &dispatcher) noexcept;

        ~scripting_system() noexcept final = default;

        void update() noexcept final;

        sol::state& get_state() noexcept
        {
            return lua_state_;
        }

        template<typename TypeToRegister>
        void register_type() noexcept
        {
            constexpr refl::type_descriptor<TypeToRegister> info = refl::reflect<TypeToRegister>();
            auto members_tpl = refl::util::map_to_tuple(refl::type_descriptor<TypeToRegister>::members, [](auto member) {
                return std::make_tuple(member.name.c_str(), member.pointer);
            });
            const auto table = std::tuple_cat(
                    std::make_tuple(info.name.str()),
                    members_tpl);

            const auto final_table = metaprog::merge_tuple(std::move(table));
            try {
                std::apply(
                        [this](auto &&...params) {
                            this->lua_state_.new_usertype<TypeToRegister>(std::forward<decltype(params)>(params)...);
                        }, final_table);
            }
            catch(const std::exception& error) {
                std::cerr << error.what() << std::endl;
            }
        }

    private:
        sol::state lua_state_;
    };
}

Seem's to work but if we have a better solution can be cool

veselink1 commented 4 years ago

Hmm, I might be missing something, but if all of that is just to pass in the parameters to the new_usertype function, could you not just use something similar to the following snippet:

public:
template<typename TypeToRegister>
void register_type() noexcept
{
    register_type_impl<TypeToRegister>(refl::reflect<TypeToRegister>().members);
}

private:
template <typename Type, typename... Members>
void register_type_impl(refl::type_list<Members>)
{
    auto params = std::tuple_cat(
                std::make_tuple(refl::reflect<Type>().name.c_str()),
                std::make_tuple(Members::name.c_str(), Members::pointer)...);
    try {
        std::apply(
            [this](auto&&... params) {
                this->lua_state_.new_usertype<Type>(std::forward<decltype(params)>(params)...);
            }, final_table);
    } 
    catch(const std::exception& error) {
        std::cerr << error.what() << std::endl;
    }
}
Milerius commented 4 years ago

But members will iterate over each members, and in your solution we will register multiple time the same type no ?

Milerius commented 4 years ago

The problem is that sometimes i need to skip the namespace for the name of the type

veselink1 commented 4 years ago

No, it would not. The only expansion is on the std::make_tuple(Members::name.c_str(), Members::pointer)... line. But I am finding it difficult to understand the need for wrap_and_flatten_tuple. Why would special handing be needed?

Milerius commented 4 years ago

Because i had a tuple of tuple with my solution and i need only one tuple, that's why i was using wrap_and_flatten_tuple

Milerius commented 4 years ago

is it possible to skip namespace for the name a type ?

veselink1 commented 4 years ago

he problem is that sometimes i need to skip the namespace for the name of the type

Yes, I should probably consider exposing a non-namespace qualified name. No, it is not available OOTB currently.

Milerius commented 4 years ago

ok if you want i had a magic snippet that i was using in my reflection system:

 namespace details
    {
        constexpr std::string_view skipNamespaceName(const std::string_view v) noexcept
        {
            return (v[0] == ':' && v[1] == ':') ? std::string_view{v.data() + 2, v.length() - 2}
                                                : skipNamespaceName({v.data() + 1, v.length() - 1});
        }
    }
veselink1 commented 4 years ago

Because i had a tuple of tuple with my solution and i need only one tuple, that's why i was using wrap_and_flatten_tuple

I think you could have gotten away with an std::apply and std::tuple_cat. I will now proceed to close the issue since you managed to get it to work.

Milerius commented 4 years ago

Yeah thank's no problem