Search code examples
c++cereal

C++ Cereal save/load_and_construct not working


So I'm trying to use the Cereal library and I've come to an issue I can't seem to overcome. Essentially the doc's say it is possible to deserialize Types with no default constructor. Yet in the implementation notes it says Define a serialize or save/load pair as you normally would yet the serialize/load options cannot be defined in a valid manner if there is no default constructor. I take this to mean, the load_and_construct function takes the place of load. Yet when implementing a relatively simple example seen below.

"main.cpp"

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <memory>

#include <cereal/access.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/memory.hpp>
#include <cereal/archives/portable_binary.hpp>

struct string_wrapper {
    const std::string str;

    string_wrapper(const std::string& _a) : str{_a} {}

    template <class Archive>
    void save(Archive& _archive) const {
        _archive(str);
    }

    template <class Archive>
    static void load_and_construct(Archive& _archive,
        cereal::construct<string_wrapper>& _construct) {
        std::string a;
        _archive(a);
        _construct(a);
    }
};

struct wrapper_of_string_wrappers {
    const std::vector<string_wrapper> strs;

    wrapper_of_string_wrappers(
        const std::vector<string_wrapper>& _a
    ) : strs{_a} { }

    template <class Archive>
    void save(Archive& _archive) const {
        _archive(strs);
    }

    template <class Archive>
    static void load_and_construct(Archive& _archive,
        cereal::construct<wrapper_of_string_wrappers>& _construct) {
        std::vector<string_wrapper> strs;
        _archive(strs);
        _construct(strs);
    }
};


int main() {

    auto file = "test.bin";

    { // save
        std::ofstream os(file, std::ios::binary);
        cereal::PortableBinaryOutputArchive archiveSave(os);

        std::vector<string_wrapper> as;
        as.push_back({"Hello"});
        as.push_back({"World"});

        wrapper_of_string_wrappers test(as);

        auto test_ptr = std::make_unique<wrapper_of_string_wrappers>(test);
        archiveSave(test_ptr);
    }

    { // load
        std::ifstream is(file, std::ios::binary);
        cereal::PortableBinaryInputArchive archiveLoad(is);

        std::unique_ptr<wrapper_of_string_wrappers> test = nullptr;
        archiveLoad(test);
        std::cout << (*test).strs[0].str << " " << (*test).strs[1].str << std::endl;
    }

    std::cin.get();

    return 0;
}

This code obviously is kind of pointless, its just a minimal example to illustrate the problem I'm running into.


Solution

  • From this page

    Non-default constructors are currently only supported for serializing pointers

    Your problem here is you are trying to serialize non pointer values with no default constructor here

    std::vector<string_wrapper> strs;
    _archive(strs);
    

    To solve your problem you need either make default constructor for string_wrapper with save/load pair or use string_wrapper as pointer in wrapper_of_string_wrappers.

    Here is working code for second option(string_wrapper remains same):

    struct wrapper_of_string_wrappers {
        //const std::vector<std::unique_ptr<string_wrapper>> strs;
        //const string_wrapper strs;
        const std::unique_ptr<string_wrapper> strs;
    
        wrapper_of_string_wrappers(
            //const std::vector<std::unique_ptr<string_wrapper>>& _a
            const string_wrapper _a
        ) : strs{ new string_wrapper(_a) } { }
    
        wrapper_of_string_wrappers(
            const wrapper_of_string_wrappers& w
        ) : strs{ new string_wrapper(*w.strs) } { }
    
        template <class Archive>
        void save(Archive& _archive) const {
            _archive(strs);
        }
    
        template <class Archive>
        static void load_and_construct(Archive& _archive,
            cereal::construct<wrapper_of_string_wrappers>& _construct) {
            //std::vector<std::unique_ptr<string_wrapper>> strs;
            std::unique_ptr<string_wrapper> strs;
            _archive(strs);
            _construct(*strs);
        }
    };
    
    int main() {
    
        auto file = "test.bin";
        { // save
            std::ofstream os(file, std::ios::binary);
            cereal::PortableBinaryOutputArchive archiveSave(os);
    
            string_wrapper as("Hello");
            wrapper_of_string_wrappers test(as);
    
            auto test_ptr = std::make_unique<wrapper_of_string_wrappers>(test);
            archiveSave(test_ptr);
        }
    
        { // load
            std::ifstream is(file, std::ios::binary);
            cereal::PortableBinaryInputArchive archiveLoad(is);
            std::unique_ptr<wrapper_of_string_wrappers> test = nullptr;
            archiveLoad(test);
            std::cout << (*test).strs->str << std::endl;
        }
    
        std::cin.get();
    
        return 0;
    }