0

I would like to use the cereal library from this github page to load xml into objects. Up to this point everything is fine.

But in my application, it is a bit more complex: the object that needs to be loaded/filled by the xml file, have to be accessed through polymorphic pointer. Therefore, if a use a raw pointer, the cereal lib refuse to take it, and ask for a smart pointer. But, when I give a smart pointer to the generic load cereal function (ie void serialize( Archive & ar ) related to cereal::XMLInputArchive::operator()), it tries to load into the whole pointer itself rather than the pointing object.

Here is an MWE:

#include <string.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <map>
#include <memory>

#include <cereal/archives/xml.hpp>
#include <cereal/types/memory.hpp>
#include <cereal/types/polymorphic.hpp>

using namespace std;

class SuperParentClass
{
    public:
        virtual std::string get_nameClass() =0;
        virtual int run() = 0;
        void serialize() ;
};

class ClassRectangle : public SuperParentClass
{
    public:
        std::string get_nameClass(){return "Rectangle";};
        double length=0.;
        double width=0.;
        
        template <class Archive>
        void serialize( Archive & ar )
        {
            ar(  CEREAL_NVP( length )  );
            ar(  CEREAL_NVP( width )  );
        }

        int run()
        {
            std::cout << "I am a Rectangle with length "<<length<<" and width "<<width << std::endl;
            return 0;
        }
};
CEREAL_REGISTER_TYPE(ClassRectangle)
CEREAL_REGISTER_POLYMORPHIC_RELATION(SuperParentClass, ClassRectangle)


int main(void) 
{ 
    // Beginning of main.
    cout << "(Start)" << endl;
    
    // Loading from file Part.
    {
        cout << "Load " << endl; 
        std::ifstream is("input.xml");
        cereal::XMLInputArchive arr(is); 

        ClassRectangle Rec;
        arr( Rec ); // or // arr( cereal::make_nvp("Rectangle", Rec) );
        Rec.run();

        // Now, a bit more complex but closer to my application, because I need a polymorphic pointer !
        std::unique_ptr<SuperParentClass> UPSPC(new ClassRectangle());
        arr( cereal::make_nvp("Rectangle", UPSPC ) );  // ask for a polymorphic_id, it does not apply the right function
        // arr( cereal::make_nvp("Rectangle", UPSPC.get() ) );  // exempt a smart pointer not a raw pointer
        // arr( cereal::make_nvp("Rectangle", &UPSPC ) );  // exempt a smart pointer not a raw pointer
        // arr( cereal::make_nvp("Rectangle", std::move(UPSPC) ) );  // ask for a polymorphic_id, it does not apply the right function
        // arr( cereal::make_nvp("Rectangle", *UPSPC.get() ) );  // does not compile.
        // arr( UPSPC ); // create an Segmentation Error, because cereal is looking for a polymorphic_id I guess.
        UPSPC->run();
    }

    // End of the main.
    cout << "(End) " << endl;   
    return 0; 
} 
// EoF

with input.xml:

<?xml version="1.0" encoding="utf-8"?>
<cereal>
    <Rectangle>
        <length>2</length>
        <width>11</width>
    </Rectangle>
    <Rectangle>
        <length>12</length>
        <width>10</width>
    </Rectangle>
</cereal>

and the signature of make_nvp from cereal.hpp:

template <class T> inline
 NameValuePair<T> make_nvp( std::string const & name, T && value )
{
  return {name.c_str(), std::forward<T>(value)};
}

The exact error message of the above MWE is :

 terminate called after throwing an instance of 'cereal::Exception'
  what():  XML Parsing failed - provided NVP (polymorphic_id) not found
 Abandon

So my question is: how can I pass the object, from a unique_ptr, into the cereal function by value ? Or is there another way to work around this ?

R. N
  • 707
  • 11
  • 31
  • 2
    1) Please reduce this to a [MCRE](https://stackoverflow.com/help/minimal-reproducible-example). This question currently contains a lot of irrelevant detail and is missing the actually important detail of what the signature of `cereal::make_nvp` is. 2) [`make_nvp`](https://www.boost.org/doc/libs/1_76_0/libs/core/doc/html/core/nvp.html) does *not* take by-value; it takes by reference. 3) Smart pointers are meant to have similar syntax to normal pointers when accessing the pointed-to object(s). How do you *normally* get a value out of a pointer? – HTNW Aug 08 '21 at 19:39
  • 1
    Maybe `arr( cereal::make_nvp("Rectangle", *UPSPC.get() ) );`? – Paul Sanders Aug 08 '21 at 19:57
  • @PaulSanders, it returns a compilation error `error: static assertion failed: cereal could not find any input serialization functions for the provided type and archive combination. ` – R. N Aug 08 '21 at 20:31
  • @HTNW thanks for the improvements suggestions. 1) I will try to reduce it as much as I can. 2) OK, my bad. But does it change something here ? 3) using `*` but it does not seems to help me in this case. – R. N Aug 08 '21 at 20:40
  • 2
    Why not **read the library documentation**: https://uscilab.github.io/cereal/polymorphism.html? If you read it yourself, then we won't have to read it just to answer you. – Phil1970 Aug 08 '21 at 20:50
  • @Phil1970 I read it, so maybe I missed something and so you can help me. Otherwise, I tried several attempts using the webpage you are pointing. And I from my tries, it does not fit with my problem. For example if you try to load two times the same class but not the same object (with different attributes, like in my example), you will realize that it not loading the attributes correctly. So sorry, if I am missing something, but if you know how to help me from your experience or the documentation, please do. – R. N Aug 08 '21 at 21:07
  • 1
    It might be helpful to augment *"the generic load cereal function"* with the name of that function. (Even after reading your code, I cannot tell whether you are referring to `cereal::XMLInputArchive::operator()` or to `cereal::make_nvp()`. – JaMiT Aug 08 '21 at 22:23
  • 1
    You should give the exact error message instead of your interpretation of it. If I understood enough of what you wrote, then the error message for the line that is not commented might be sufficient. – JaMiT Aug 08 '21 at 22:26
  • 1
    @R.N **The documentation explain registration steps that are not present in your code**. The library cannot guess class hierarchy if you don't add lines like `CEREAL_REGISTER_POLYMORPHIC_RELATION(BaseClass, DerivedClassOne)`. I have no experience with cereal but it is very easy to see from the documentation that your code is incomplete. – Phil1970 Aug 08 '21 at 22:26
  • @Phil1970 Yes, the documentation are mentioning steps that are not present in my code, because I found them not relevant with my problem. But since your trying to help me (I really appreciate), I edit my question to include the polymorphic instruction required by `cereal` (I hope it will help you back). It does not change the error message that I get. Because, the structure of the input file is really different in the polymorphic case given by the documentation: it is storing and loading the current state of a pointer. I try to load the object target by the pointer. – R. N Aug 08 '21 at 23:02
  • @JaMiT I edited my question to include the exact error message I get at the execution. (Sorry I should have done it since the begging). And I tried to clarify the generic loading function. I am referring to the `serialize` function itself used by `cereal` to save and load for several kind of output file format. So it is related to `cereal::XMLInputArchive::operator()`. In fact, the all the `cereal::make_nvp()` are just here to help debugging since it gives names to the variables. I also tried without, and it makes no difference. – R. N Aug 08 '21 at 23:15
  • Was the XML file generated by cereal or you write an arbitrary XML hoping it works? Are you sure that it is valid to have 2 nodes at the same level with the same name (`` in your case)? I haven't see that in the documentation. Usually, it should be easier to start writing code that save data first. Also, useually, if you get an exception, it should be much easier to understand the problem using a debugger. – Phil1970 Aug 09 '21 at 00:34
  • @Phil1970 I did generated an XML file with cereal in a first place. And I am trying now to reload this generated file. Also, it is completely valid to have 2 nodes at the same level with the same name. If you want to convince yourself, copy paste the part of my example `ClassRectangle Rec; arr( Rec ); Rec.run();` a second time and comment the end. – R. N Aug 09 '21 at 09:50
  • You already ask a similar question a few days ago. So you are wasting people time with this question as the other question show that you already knew it is a limitation of the library: https://stackoverflow.com/questions/68655182/c-cereal-how-to-load-polymorphic-class-without-knowing-its-polymorphic-id. And also the issue on GitHub: https://github.com/USCiLab/cereal/issues/709. – Phil1970 Aug 09 '21 at 23:53
  • @Phil1970 it is not similar question. In the previous question, I ask for a manner to know the polymorphic_id in advance, while here I try to give the object targeted by a pointer to a function so the function will never see the pointer. The two questions are both using the same library, that's true, but are quite different to me. I definitively don't want to wast people time. – R. N Aug 10 '21 at 08:43

1 Answers1

1

The problem is that you don't load the file the same way you write it. If you write it using smart pointers, then the file contains additionnal data including a node polymorphic_id. You would have a file similar to this one:

<?xml version="1.0" encoding="utf-8"?>
<cereal>
    <value0>
        <polymorphic_id>2147483649</polymorphic_id>
        <polymorphic_name>Rectangle</polymorphic_name>
        <ptr_wrapper>
            <valid>1</valid>
            <data>
                <length>1</length>
                <width>5</width>
            </data>
        </ptr_wrapper>
    </value0>
    <value1>
        <polymorphic_id>1</polymorphic_id>
        <ptr_wrapper>
            <valid>1</valid>
            <data>
                <length>2</length>
                <width>25</width>
            </data>
        </ptr_wrapper>
    </value1>
</cereal>

after modifying the registration to use

 CEREAL_REGISTER_TYPE_WITH_NAME(ClassRectangle, "Rectangle")

instead of:

 CEREAL_REGISTER_TYPE(ClassRectangle)

The code I used to write the file is:

    std::ofstream is("output.xml");
    cereal::XMLOutputArchive arr(is);

    auto *rp1 = new ClassRectangle();
    rp1->length = 1;
    rp1->width = 5;
    std::unique_ptr<SuperParentClass> p1(rp1);

    auto *rp2 = new ClassRectangle();
    rp2->length = 2;
    rp2->width = 25;
    std::unique_ptr<SuperParentClass> p2(rp2);

    arr(p1);
    arr(p2);

Then that file can be read back using smart pointers to SuperParentClass.

Alternatively, if you want to read the original file in a smart pointer, it can easily be done by using the following lines:

    std::unique_ptr<ClassRectangle> UPSPC(new ClassRectangle());
    arr(*UPSPC);
    UPSPC->run();

Obviously, you have to use the derived type here because the file was saved without hierarchical information needed to handle polymorphic type.

Also notice the * in the expression arr(*UPSPC). That way, you are loading into an already allocated object.

Update

If you have at most one obect of each derived type, then you could load them by name:

    std::unique_ptr<ClassTriangle> tri(new ClassTriangle());
    std::unique_ptr<ClassRectangle> rect(new ClassRectangle());

    arr(cereal::make_nvp("Triangle", *tri));
    arr(cereal::make_nvp("Rectangle", *rect));

    rect->run();
    tri->run();
Phil1970
  • 2,605
  • 2
  • 14
  • 15
  • Your solution is working on paper, but is not solving my problem. 1) you use `std::unique_ptr UPSPC` instead of `std::unique_ptr UPSPC`. This is important since in my real problem I must use the parent class for other reasons. 2) I don't know how to obtain in advance the `polymorphic_id` used in the input file. You obtained it from a saving operation. But I my real application I load to obtain information for users, so I can not know the saving operation corresponding (ie the `polymorphic_id`), I just know the name of the objects (Rectangle or other). – R. N Aug 09 '21 at 21:01
  • Then you are probably using the wrong library for the job... From the documentation and some trials, it is clear that `cereal` expect a specific format for polymorphic loading. Information on the node name does not seems to be directly available to user of the library. An archiving library like `cereal` is intended for a specific purposed of saving and loading versionned data. **You probably want a general XML library instead.** – Phil1970 Aug 09 '21 at 22:46
  • I have found that if you have at most one object of each derived class, you can load them all in specific smart pointers. If none of the approach works for you, then you probably should use a generic library instead (and then remove or rename misleading node `cereal` as you don't follow that format anymore). – Phil1970 Aug 09 '21 at 23:22
  • Having one object of each derived class and load them in a smart pointer could be a good start. But the issue is the "specific" smart pointer (ie `std::unique_ptr` vs `std::unique_ptr`)... So if I have to switch to a general XML library, do you have recommendations ? Otherwise, my initials ideas was to give the object targeted by the smart pointer to the function, so the function will not ask for a polymorphic_id since it will be an object and not a pointer anymore. Or to overload the cereal function for pointers. But in both cases I don't know how to do. – R. N Aug 10 '21 at 08:36