boostorg / describe

A C++14 reflection library
https://boost.org/libs/describe
67 stars 24 forks source link

Trait for described types #1

Closed vinipsmaker closed 2 years ago

vinipsmaker commented 3 years ago

Right now I can create overload for functions that will only participate if there is reflection for them with Hana (trait boost::hana::Struct<T>). It'd be nice if describe also have a trait for types that were "described".

pdimov commented 3 years ago

The describe_... traits cause a substitution failure if invoked on a type that hasn't been annotated. You can use this to restrict overloads.

template<class E, class D = describe_enumerators<E>> void f( E e ) { ... }
vinipsmaker commented 3 years ago

That's a little of inconvenience thou. Couldn't you make it easier by converting this pattern into a trait as Hana does? It's fairly straightforward to combine multiple (possibly negated) traits through std::enable_if. They do read easier.

sdebionne commented 3 years ago

The describe_... traits cause a substitution failure if invoked on a type that hasn't been annotated.

Should'nt the following code do the trick (apparently it's not) ?

namespace boost::describe
{
    template <typename T, typename Enable = void>
    struct is_described : std::false_type {};

    template <typename T>
    struct is_described<T, describe_members<T, mod_any_access>> : std::true_type {};

    template <class T>
    inline constexpr bool is_described_v = is_described<T>::value;
}
pdimov commented 3 years ago

You need to wrap describe_members<T, mod_any_access> in a void_t, because it doesn't match the primary void otherwise.

sdebionne commented 3 years ago

Thank you! For the records, here is a working is_described<>:

namespace boost::describe
{
    template <typename T, typename Enable = void>
    struct is_described : std::false_type {};

    template <typename T>
    struct is_described<T, std::void_t<describe_members<T, mod_any_access>>> : std::true_type {};

    template <class T>
    inline constexpr bool is_described_v = is_described<T>::value;
}

I personally think is_described<> can be useful, for instance when reflecting recursively:

template <class T, typename Md = boost::describe::describe_members<T, boost::describe::mod_any_access>>
void print(std::ostream& os, T const& t, int indent = 0)
{
    bool first = true;
    boost::mp11::mp_for_each<Md>([&](auto D) {
        for (int i = 0 ; i < indent ; i++)
            os << "\t";

        os << "." << D.name << " = " << t.*D.pointer << std::endl;

        // Recursively print class members if they are described
        using return_t = std::decay_t<boost::callable_traits::return_type_t<decltype(D.pointer)>>;
        if constexpr (std::is_class_v<return_t> && boost::describe::is_described_v<return_t>)
            print(os, t.*D.pointer, ++indent);
    });
}

It's pretty straightforward to implement but would you be interested in a PR for is_described<> that would support struct/class and enumerators?

pdimov commented 2 years ago

This is now implemented as three separate traits has_describe_enumerators, has_describe_bases and has_describe_members. The latter two in practice give the same result and are effectively equivalent to the is_described trait above.