Boost serialization with empty container

44 Views Asked by At

I am using boost ( 1.69.0 ) serialization to generate an XML template file. The result I want to achieve is unfortunately bad. This is example code:

struct A
{
    struct B
    {

        int bInt;
        bool bBool;

    private:
        friend class boost::serialization::access;


        template < class Archive >
        void serialize( Archive& ar, const unsigned int version )
        {
            ar& BOOST_SERIALIZATION_NVP( bInt );
            ar& BOOST_SERIALIZATION_NVP( bBool );

        }
    };

    std::vector< B > aVector;
    int aInt;
    bool abool;

private:
    friend class boost::serialization::access;

    template < class Archive >
    void serialize( Archive& ar, const unsigned int version )
    {
        ar& BOOST_SERIALIZATION_NVP( aVector );
        ar& BOOST_SERIALIZATION_NVP( aInt );
        ar& BOOST_SERIALIZATION_NVP( abool );

    }
};

And calling

void createXml( const std::filesystem::path& filePath )
{
    if (filePath.empty())
    {
        return;
    }

    A data;
    wofstream wofFile( filePath.c_str() );
    boost::archive::xml_woarchive warOutArchive( wofFile );
    warOutArchive << boost::serialization::make_nvp( "_Template", data );
}

Result:

<boost_serialization signature="serialization::archive" version="17">
<_Template class_id="0" tracking_level="0" version="0">
    <aVector class_id="1" tracking_level="0" version="0">
        <count>0</count>
        <item_version>0</item_version>
    </aVector>
    <aInt>-858993460</aInt>
    <abool>204</abool>
</_Template>
</boost_serialization>

As you can see, the template generates incorrectly. Content is missing

Everything works well. when I add an element to an empty vector.

sData.aVector.push_back( { 5, true } );

Result:

<boost_serialization signature="serialization::archive" version="17">
<_Template class_id="0" tracking_level="0" version="0">
    <aVector class_id="1" tracking_level="0" version="0">
        <count>1</count>
        <item_version>0</item_version>
        <item class_id="2" tracking_level="0" version="0">
            <bint>5</bint>
            <bbool>1</bbool>
        </item>
    </aVector>
    <aInt>-858993460</aInt>
    <abool>204</abool>
</_Template>
</boost_serialization>

Is there any way to generate an xml file template without adding elements to containers? I only need the document template/tree, no values.

1

There are 1 best solutions below

0
sehe On

Do you ... just want to infer an XML schema for the file? Boost Serialization doesn't have this. You can however prepopulate all your members and then use an existing tool to infer a schema from the example output:

Any tools to generate an XSD schema from an XML instance document?

To automatically prepopulate any container elements you could write a helper function like:

namespace detail {
    /// simplistic trait to detect if a type is a container
    template <typename, typename Enable = void> struct is_container : std::false_type {};
    template <typename T>
    struct is_container<T,
                        decltype(                                  //
                            decltype(std::declval<T>().begin()){}, //
                            decltype(std::declval<T>().end()){},   //
                            decltype(std::declval<T>().size()){},  //
                            void())> : std::true_type {};

    // trivial archive implementation that just prepopulates container objects
    struct prepopulater {
        using is_saving  = std::true_type;
        using is_loading = std::false_type;
        template <class T> void          register_type() {}
        template <class Deduced> prepopulater& operator<<(Deduced&& v) {
            using T = std::decay_t<Deduced>;

            if constexpr (is_container<T>{}) {
                using V = std::decay_t<decltype(*v.begin())>;
                auto& mv = const_cast<T&>(v);
                std::cout << "Prepopulate " << boost::core::demangle(typeid(T).name()) << std::endl;
                mv.insert(mv.end(), V{});
            }
            using boost::serialization::serialize;
            constexpr auto IL = boost::serialization::implementation_level<T>::value;
            if constexpr (IL != boost::serialization::not_serializable &&
                          IL != boost::serialization::primitive_type) {
                auto& mv = const_cast<T&>(v);
                serialize(*this, mv, 0);
            }
            return *this;
        }
        template <class T> prepopulater& operator&(T&& v) { return *this << std ::forward<T>(v); }

        template <class T> prepopulater& operator<<(boost::serialization::nvp<T> t) {
            return *this << t.value();
        }
        template <class T> prepopulater& operator&(boost::serialization::nvp<T> t) {
            return *this << t.value();
        }

        void save_binary(void*, size_t){};
    };

} // namespace detail

template <typename Serializable> void prepopulate(Serializable& obj) { detail::prepopulater{} << obj; }

Note that this runs into trouble when you have recursive data structures, because it will run out of memory infinitely deepening. This, however, is the nature of the requirement that your question poses.

For example, replacing int bInt with std::multiset<int> bInts just for better demo:

Live On Coliru

#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
// #include <boost/core/demangle.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/set.hpp>
#include <filesystem>
#include <fstream>
#include <iostream>

namespace detail {
    /// simplistic trait to detect if a type is a container
    template <typename, typename Enable = void> struct is_container : std::false_type {};
    template <typename T>
    struct is_container<T,
                        decltype(                                  //
                            decltype(std::declval<T>().begin()){}, //
                            decltype(std::declval<T>().end()){},   //
                            decltype(std::declval<T>().size()){},  //
                            void())> : std::true_type {};

    // trivial archive implementation that just prepopulates container objects
    struct prepopulater {
        using is_saving  = std::true_type;
        using is_loading = std::false_type;
        template <class T> void          register_type() {}
        template <class Deduced> prepopulater& operator<<(Deduced&& v) {
            using T = std::decay_t<Deduced>;

            if constexpr (is_container<T>{}) {
                using V = std::decay_t<decltype(*v.begin())>;
                auto& mv = const_cast<T&>(v);
                // std::cout << "Prepopulate " << boost::core::demangle(typeid(T).name()) << std::endl;
                mv.insert(mv.end(), V{});
            }
            using boost::serialization::serialize;
            constexpr auto IL = boost::serialization::implementation_level<T>::value;
            if constexpr (IL != boost::serialization::not_serializable &&
                          IL != boost::serialization::primitive_type) {
                auto& mv = const_cast<T&>(v);
                serialize(*this, mv, 0);
            }
            return *this;
        }
        template <class T> prepopulater& operator&(T&& v) { return *this << std ::forward<T>(v); }

        template <class T> prepopulater& operator<<(boost::serialization::nvp<T> t) {
            return *this << t.value();
        }
        template <class T> prepopulater& operator&(boost::serialization::nvp<T> t) {
            return *this << t.value();
        }

        void save_binary(void*, size_t){};
    };

} // namespace detail

template <typename Serializable> void prepopulate(Serializable& obj) { detail::prepopulater{} << obj; }

struct A {
    struct B {
        std::multiset<int> bInts;
        bool               bBool;
        template <class Ar> void serialize(Ar& ar, unsigned) { ar& BOOST_NVP(bInts) & BOOST_NVP(bBool); }
    };

    std::vector<B> aVector;
    int            aInt;
    bool           abool;
    template <class Ar> void serialize(Ar& ar, unsigned) { ar& BOOST_NVP(aVector) & BOOST_NVP(aInt) & BOOST_NVP(abool); }
};

void createTemplateXml(std::filesystem::path const& filePath) {
    A data;
    prepopulate(data);

    std::ofstream ofFile(filePath);
    boost::archive::xml_oarchive(ofFile) << boost::serialization::make_nvp("_Template", data);
}

int main() {
    createTemplateXml("prepopulated.xml");
}

Which writes

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="20">
<_Template class_id="0" tracking_level="0" version="0">
    <aVector class_id="1" tracking_level="0" version="0">
        <count>1</count>
        <item_version>0</item_version>
        <item class_id="2" tracking_level="0" version="0">
            <bInts>
                <count>1</count>
                <item_version>0</item_version>
                <item>0</item>
            </bInts>
            <bBool>0</bBool>
        </item>
    </aVector>
    <aInt>0</aInt>
    <abool>0</abool>
</_Template>
</boost_serialization>