BOOST_DEFINE_ENUM_CLASS и JSON

В документация для boost описывает под заголовком «Автоматическое преобразование в JSON» показано, как реализовать «универсальную перегрузку tag_invoke, которая автоматически преобразует аннотированную структуру в значение Boost.JSON». Пример поддерживает BOOST_DESCRIBE_STRUCT, как реализовать нечто подобное для BOOST_DEFINE_ENUM_CLASS?

Вот моя наивная попытка адаптировать пример для поддержки BOOST_DEFINE_ENUM_CLASS наряду с BOOST_DESCRIBE_STRUCT:

#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/json.hpp>
#include <type_traits>
#include <vector>
#include <map>

namespace app {
    template<class T,
        class D1 = boost::describe::describe_members<T, boost::describe::mod_public | boost::describe::mod_protected>,
        class D2 = boost::describe::describe_members<T, boost::describe::mod_private>,
        class En = std::enable_if_t<boost::mp11::mp_empty<D2>::value> >

    void tag_invoke(boost::json::value_from_tag const&, boost::json::value& v, T const& t) {
        auto& obj = v.emplace_object();
        boost::mp11::mp_for_each<D1>([&](auto D) {
            obj[D.name] = boost::json::value_from(t.*D.pointer);
        });
    }

    struct A {
        int x;
        int y;
    };
    BOOST_DESCRIBE_STRUCT(A, (), (x, y))

    struct B {
        std::vector<A> v;
        std::map<std::string, A> m;
    };
    BOOST_DESCRIBE_STRUCT(B, (), (v, m))

    BOOST_DEFINE_ENUM_CLASS(E1, v1, v2, v3)
    struct C {
        int x;
        E1 e1;
    };
    BOOST_DESCRIBE_STRUCT(C, (), (x, e1))

} // namespace app

#include <iostream>

void main() {
    app::A a{ 1, 2 };
    std::cout << boost::json::value_from(a) << std::endl;
    app::B b{ { { 1, 2 }, { 3, 4 } }, { { "k1", { 5, 6 } }, { "k2", { 7, 8 } } } };
    std::cout << boost::json::value_from(b) << std::endl;
    app::C c{ 1, app::E1::v1 };
    //std::cout << boost::json::value_from(c) << std::endl;
}

Что мне нужно сделать, чтобы включить boost::json::value_from() для c?

Редактировать: Окей, код ниже делает свое дело, но я должен сделать его более шаблонным:

void tag_invoke(boost::json::value_from_tag const&, boost::json::value& v, E1 const& t) {
    v = boost::describe::enum_to_string(t, "x");
}

🤔 А знаете ли вы, что...
Язык C++ обеспечивает совместимость с языком C, что позволяет использовать код на обоих языках в одном проекте.


1
35
1

Ответ:

Решено

В документации приведен этот пример (слегка измененный в соответствии с нашими потребностями JSON позже):

template <class E> char const* enum_to_string(E e) {
    char const* r = nullptr;

    boost::mp11::mp_for_each<boost::describe::describe_enumerators<E>>(
        [&](auto D) {
            if (e == D.value)
                r = D.name;
        });

    return r;
}

Вы можете использовать это для реализации точки настройки value_from:

static inline void tag_invoke(json::value_from_tag, json::value& jv, E1 e) {
    auto name = enum_to_string(e);

    jv = name
        ? name
        : std::to_string(static_cast<std::underlying_type_t<E1>>(e));
}

При наличии подходящего SFINAE вы можете создать шаблон для любого типа перечисления, если вы помните об ограничении применимости ADL к вашим собственным перечислениям, чтобы не мешать другим библиотекам.

Живая демонстрация:

Live On Compiler Explorer

#include <boost/describe.hpp>
#include <boost/json.hpp>
#include <boost/json/src.hpp> // for online demo
#include <boost/mp11.hpp>
#include <map>
#include <type_traits>
#include <vector>

namespace app {
    namespace json = boost::json;

    template<class T,
        class D1 = boost::describe::describe_members<T, boost::describe::mod_public | boost::describe::mod_protected>,
        class D2 = boost::describe::describe_members<T, boost::describe::mod_private>,
        class En = std::enable_if_t<boost::mp11::mp_empty<D2>::value> >

    void tag_invoke(json::value_from_tag const&, json::value& v, T const& t) {
        auto& obj = v.emplace_object();
        boost::mp11::mp_for_each<D1>([&](auto D) {
            obj[D.name] = json::value_from(t.*D.pointer);
        });
    }

    struct A {
        int x;
        int y;
    };
    BOOST_DESCRIBE_STRUCT(A, (), (x, y))

    struct B {
        std::vector<A> v;
        std::map<std::string, A> m;
    };
    BOOST_DESCRIBE_STRUCT(B, (), (v, m))

    BOOST_DEFINE_ENUM_CLASS(E1, v1, v2, v3)

    template <class E> char const* enum_to_string(E e) {
        char const* r = nullptr;

        boost::mp11::mp_for_each<boost::describe::describe_enumerators<E>>(
            [&](auto D) {
                if (e == D.value)
                    r = D.name;
            });

        return r;
    }

    static inline void tag_invoke(json::value_from_tag, json::value& jv, E1 e) {
        auto name = enum_to_string(e);

        jv = name
            ? name
            : std::to_string(static_cast<std::underlying_type_t<E1>>(e));
    }

    struct C {
        int x;
        E1 e1;
    };
    BOOST_DESCRIBE_STRUCT(C, (), (x, e1))

} // namespace app

#include <iostream>

int main() {
    app::A a{ 1, 2 };
    std::cout << boost::json::value_from(a) << std::endl;
    app::B b{ { { 1, 2 }, { 3, 4 } }, { { "k1", { 5, 6 } }, { "k2", { 7, 8 } } } };
    std::cout << boost::json::value_from(b) << std::endl;
    app::C c{ 1, app::E1::v1 };
    std::cout << boost::json::value_from(c) << std::endl;
}

Отпечатки

{"x":1,"y":2}
{"v":[{"x":1,"y":2},{"x":3,"y":4}],"m":{"k1":{"x":5,"y":6},"k2":{"x":7,"y":8}}}
{"x":1,"e1":"v1"}