boost::describe: Can I add annotations to types and/or members?

214 Views Asked by At

Is it possible, using the boost::describe library, to add additional information about a given type or member?

For instance, if I have a class described like this:

BOOST_DESCRIBE_STRUCT(Foo, (), (bar, baz))

Is there any way to add some kind of additional info, like a tag or flag, to either the members or the type? There's a lot of information about members, such as name, type, visibility, etc. Is there a way to add some custom info to that?

To flag types, I've tried using type traits with std::true_type or std::false type.

However, these are cumbersome to use, because template specializations must be done in the namespace where the type traits were defined, not the namespace where the types being described are defined.

1

There are 1 best solutions below

5
On

You could use a trait that maps by descriptor. Let me show a contrived complex example that combines multiple pieces of meta data and even behavior:

Live On Coliru

#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <iomanip>
#include <iostream>

namespace bd   = boost::describe;
namespace mp11 = boost::mp11;

namespace MyLib {
    struct Metadata {
        bool             some_flag = false;
        std::string_view some_text = "";
        static void      some_action() { std::cout << "default action" << std::endl; }
    };

    template <typename D> constexpr inline Metadata meta_v{};

    template <class T, class D1 = bd::describe_members<T, bd::mod_public | bd::mod_protected>,
              class En = std::enable_if_t<!std::is_union<T>::value>>
    void demo(T const&) {
        mp11::mp_for_each<D1>([&](auto D) {
            auto const& m = meta_v<decltype(D)>;
            std::cout << "meta for " << D.name << ": { " << m.some_flag << ", "
                      << quoted(m.some_text) << " }" << std::endl;
            if (m.some_flag)
                m.some_action();
        });
    }
} // namespace MyLib

// application
namespace MyLib {
    struct Foo {
        int         bar;
        std::string baz;
        double      qux;
    };

    BOOST_DESCRIBE_STRUCT(Foo, (), (bar, baz, qux))

    // a shorthand, you might want to make this more generically elegant by deducing `Foo` instead
    template <auto Mem>
    using Desc = bd::descriptor_by_pointer<bd::describe_members<Foo, bd::mod_any_access>, Mem>;

    // specialize some metadata
    template <> constexpr inline Metadata meta_v<Desc<&Foo::baz>>{true, "hello"};

    struct Special {
        double some_flag = 42e-2;
        std::string_view some_text = "specialized!";
        static void      some_action() { std::cerr << "stderr instead" << std::endl; }
    };
    template <> constexpr inline Special meta_v<Desc<&Foo::qux>>{};

} // namespace MyApp

int main() {
    std::cout << std::boolalpha;
    demo(MyLib::Foo{});
}

Prints

meta for bar: { false, "" }
meta for baz: { true, "hello" }
default action
meta for qux: { 0.42, "specialized!" }
stderr instead

Further Thoughts

You might also use a Fusion/Hana map to associate information to descriptors. It depends mostly on how you want your code to look of course.

UPDATE

Generalizing a bit (deducing the class type from a member pointer) and supporting member function pointers:

Live On Coliru

#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <iomanip>
#include <iostream>

namespace bd   = boost::describe;
namespace mp11 = boost::mp11;

namespace MyLib {
    template <typename D> constexpr inline bool flag_v{};

    template <
        class T, class D1 = bd::describe_members<T, bd::mod_public | bd::mod_protected>,
        class F1 = bd::describe_members<T, bd::mod_public | bd::mod_protected | bd::mod_function>,
        class En = std::enable_if_t<!std::is_union<T>::value>>
    void demo(T const&) {
        mp11::mp_for_each<D1>([&](auto D) {
            std::cout << "flag for " << D.name << ": " << flag_v<decltype(D)> << std::endl;
        });
        mp11::mp_for_each<F1>([&](auto D) {
            std::cout << "flag for " << D.name << ": " << flag_v<decltype(D)> << std::endl;
        });
    }

    namespace detail {
        template <typename C, typename T> constexpr C deduce_class(T(C::*));
        template <auto Mem> using Class = decltype(deduce_class(Mem));
        template <auto Mem, typename /*Enable*/ = void> struct DescF;

        template <auto Mem>
        struct DescF<Mem, std::enable_if_t<std::is_member_function_pointer_v<decltype(Mem)>>> {
            using type = bd::descriptor_by_pointer<
                bd::describe_members<Class<Mem>, bd::mod_any_access | bd::mod_function>, Mem>;
        };

        template <auto Mem>
        struct DescF<Mem, std::enable_if_t<not std::is_member_function_pointer_v<decltype(Mem)>>> {
            using type =
                bd::descriptor_by_pointer<bd::describe_members<Class<Mem>, bd::mod_any_access>,
                                          Mem>;
        };
    }

    template <auto Mem> using Desc = typename detail::DescF<Mem>::type;
} // namespace MyLib

// application
namespace MyApp {
    struct Foo {
        int         bar;
        std::string baz;
        double      qux;
    };

    struct Bar {
        int quuz(double) { return 42; }
    };

    BOOST_DESCRIBE_STRUCT(Foo, (), (bar, baz, qux))
    BOOST_DESCRIBE_STRUCT(Bar, (), (quuz))
} // namespace MyApp

using MyLib::Desc;

// specialize some metadata
template <> auto MyLib::flag_v<Desc<& MyApp::Foo::baz>>  = true;
template <> auto MyLib::flag_v<Desc<& MyApp::Foo::qux>>  = "surprise";
template <> auto MyLib::flag_v<Desc<& MyApp::Bar::quuz>> = "fun";

int main() {
    std::cout << std::boolalpha;
    MyLib::demo(MyApp::Foo{});
    MyLib::demo(MyApp::Bar{});
}

Prints

flag for bar: false
flag for baz: true
flag for qux: surprise
flag for quuz: fun