serializing class hierarchy with external serialize functions: cereal::base_class complains

518 views
Skip to first unread message

Jiri Hoogland

unread,
Jul 13, 2015, 11:26:36 AM7/13/15
to cere...@googlegroups.com
Hi,

I have been playing with cereal and am trying to use it for some code
I am writing. Sofar it is a great experience, very nice library!

One thing I prefer to do is keep the serialization logic as much as
possible seperated from the actual types I am working with.

For a simple type, no hierarchy, this works fine.

In the code below I have a class hierarchy of Base <- Derived <- Derived2.

In the case that I implement the code internally to the types,
things work fine. The values are serialized/deserialized properly.

However when I try to write the serialization code
as an external serialize function, I get compile errors.

I'm a bit at loss what I am missing. Looking at base_class.hpp,
base_class takes a const Base * pointer, but the Base passed into the
serialize function should not be const, I would expect. So that gives
the error. But how to solve this then ?

Maybe I have overlooked something in the documentation,
but if someone has some hints on what I m doing wrong, it would be greatly appreciated.

Thanks
Jiri

System:

$  uname -a
Darwin JiriMBP.home 14.4.0 Darwin Kernel Version 14.4.0: Thu May 28 11:35:04 PDT 2015; root:xnu-2782.30.5~1/RELEASE_X86_64 x86_64 i386 MacBookPro11,2 Darwin
$  g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.4.0
Thread model: posix

Error:
```
/Users/Jiri/sandbox/cereal/testCereal.cpp:301:42: error: no matching conversion for functional-style cast from 'Derived' to
      'cereal::base_class<Base>'
        ar( cereal::make_nvp( "Derived", cereal::base_class<Base>( d ) ),
                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/include/cereal/types/base_class.hpp:72:12: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'Derived'
      to 'const cereal::base_class<Base>' for 1st argument
    struct base_class : private traits::detail::BaseCastBase
           ^
/usr/local/include/cereal/types/base_class.hpp:72:12: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'Derived'
      to 'cereal::base_class<Base>' for 1st argument
    struct base_class : private traits::detail::BaseCastBase
           ^
/usr/local/include/cereal/types/base_class.hpp:75:9: note: candidate template ignored: could not match 'const Derived *' against 'Derived'
        base_class(Derived const * derived) :
        ^
```
        
Code:
```
#include <gtest/gtest.h>
#include <cereal/archives/binary.hpp>
#include <cereal/archives/xml.hpp>
#include <cereal/archives/json.hpp>
#include <cereal/cereal.hpp>
#include <cereal/types/base_class.hpp>
#include <cereal/types/polymorphic.hpp>
#include <cereal/types/common.hpp>

#include <fstream>
#include <sstream>

// if INTERNAL is undefined, compile errors pop up.
#define INTERNAL 1

struct Base
{
    int x;
    virtual int doSomething() const = 0;
    virtual ~Base() {}

#ifdef INTERNAL
    template <class Archive>
    void serialize( Archive& ar )
    {
        ar( cereal::make_nvp( "x", x ) );
    }
#else
    template <class Archive>
    friend void cereal::serialize( Archive& ar, Base& b );
#endif
};

struct Derived : public Base
{
    int y;
    int doSomething() const override { return 1; }

#ifdef INTERNAL
    template <class Archive>
    void serialize( Archive& ar )
    {
        ar( cereal::make_nvp( "Derived", cereal::base_class<Base>( this ) ),
            cereal::make_nvp( "y", y ) );
    }
#else
    template <class Archive>
    friend void cereal::serialize( Archive& ar, Derived& d );
#endif
};

struct Derived2 : public Derived
{
    int z;
    int doSomething() const override { return 2; }

#ifdef INTERNAL
    template <class Archive>
    void serialize( Archive& ar )
    {
        ar( cereal::make_nvp( "Derived2", cereal::base_class<Derived>( this ) ),
            cereal::make_nvp( "z", z ) );
    }
#else
    template <class Archive>
    friend void cereal::serialize( Archive& ar, Derived2& d );
#endif
};

#ifndef INTERNAL
namespace cereal
{
    template <class Archive>
    void serialize( Archive& ar, Base& b )
    {
        ar( cereal::make_nvp( "x", b.x ) );
    }

    template <class Archive>
    void serialize( Archive& ar, Derived& d )
    {
        ar( cereal::make_nvp( "Derived", cereal::base_class<Base>( d ) ),
            cereal::make_nvp( "y", d.y ) );
    }

    template <class Archive>
    void serialize( Archive& ar, Derived2& d )
    {
        ar( cereal::make_nvp( "Derived2", cereal::base_class<Derived>( d ) ),
            cereal::make_nvp( "z", d.z ) );
    }
} // namespace cereal
#endif

TEST( Cereal, TestInheritance )
{
    std::string dir( "." );
    {
        std::ofstream ofileJSON( dir + "/out.json" );
        std::ofstream ofileXML( dir + "/out.xml" );
        cereal::JSONOutputArchive oarchiveJSON( ofileJSON );
        cereal::XMLOutputArchive oarchiveXML( ofileXML );

        Derived d;
        d.x = 1234;
        d.y = 9876;
        Derived2 d2;
        d2.x = 1234;
        d2.y = 9876;
        d2.z = 1199;
        oarchiveJSON(
            cereal::make_nvp( "d", d ),
            cereal::make_nvp( "d2", d2 ) );
        oarchiveXML(
            cereal::make_nvp( "d", d ),
            cereal::make_nvp( "d2", d2 ) );
    }

    {
        std::ifstream ifileJSON( dir + "/out.json" );
        cereal::JSONInputArchive iarchiveJSON( ifileJSON ); // Create an input archive

        Derived d;
        Derived2 d2;
        iarchiveJSON( d, d2 );

        //        auto options = cereal::JSONOutputArchive::Options::NoIndent();
        auto options = cereal::JSONOutputArchive::Options( 6 );
        std::ostream& os( std::cout );
        cereal::JSONOutputArchive oarchiveJSON2( os, options );
        oarchiveJSON2(
            cereal::make_nvp( "d", d ),
            cereal::make_nvp( "d2", d2 ) );
    }
}
```

w.shan...@gmail.com

unread,
Jul 13, 2015, 11:19:12 PM7/13/15
to cere...@googlegroups.com, jiri.h...@gmail.com
Make sure when you are calling cereal::base_class that you pass it a pointer, so your external serialization calls should be something like:

ar( cereal::make_nvp( "whatever", std::addressof( d ) ) ); // or just &d, whatever floats your boat.

Your bug only shows up with the friend declarations in place if they are in the cereal namespace - if you get rid either the namespace or the friend declaration it will compile properly.  There is also no problem if you use clang with or without the friend declarations.

To resolve this you need not use a namespace for your serialization functions or you need to forward declare the functions before the friendship, which is cumbersome given that you'll need to forward declare your classes too.

Jiri Hoogland

unread,
Jul 14, 2015, 9:27:03 AM7/14/15
to cere...@googlegroups.com, jiri.h...@gmail.com
Thank you for the quick reply,
and sorry for the silly mistake I made with the reference (painful:-))

Regarding friend declarations, I use classes with private variables, hence my use.
Thanks again,
Regards
Jiri
Reply all
Reply to author
Forward
0 new messages