2

I am trying to get some (hitherto) windows code to compile on a unix machine but am getting some errors at the following method:

namespace EDIN {
    void World::Save(char const filename[])                                                  
    {                                                                                        
        std::ofstream ofs(filename);                                                         

        boost::archive::text_oarchive oa(ofs);                                               

        oa << this << cellSequence << report; // line 590: error!
    }
}

The error looks like this:

test.cpp: In member function ‘void EDIN::World::Save(const char*)’: test.cpp:122:12: error: no match for ‘operator<<’ (operand types are ‘boost::archive::text_oarchive’ and ‘EDIN::World*’) oa << this << cellSequence << report; // line 590: error! ^ test.cpp:122:12: note: candidate is: In file included from /home/armanschwarz/lib/boost_1_55_0/boost/archive/detail/common_oarchive.hpp:22:0, from /home/armanschwarz/lib/boost_1_55_0/boost/archive/basic_text_oarchive.hpp:32, from /home/armanschwarz/lib/boost_1_55_0/boost/archive/text_oarchive.hpp:31, from test.cpp:1: /home/armanschwarz/lib/boost_1_55_0/boost/archive/detail/interface_oarchive.hpp:62:15: note: Archive& boost::archive::detail::interface_oarchive::operator<<(T&) [with T = EDIN::World*; Archive = boost::archive::text_oarchive] Archive & operator<<(T & t){ ^ /home/armanschwarz/lib/boost_1_55_0/boost/archive/detail/interface_oarchive.hpp:62:15: note: no known conversion for argument 1 from ‘EDIN::World*’ to ‘EDIN::World*&’

I'm using boost 1.55.0 (same as when I used to compile this on Visual Studio). Can anyone spot what I'm doing wrong here?

Here is a self-contained example from sehe:

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/valarray.hpp>
#include <chrono>

namespace EDIN
{
    struct Region
    {
        int id;
        Region(int id = 42) : id(id) {}

      private:
        friend class boost::serialization::access;
        template<class Archive> void serialize(Archive & ar, const unsigned int file_version)
        {
            ar & id;
        }
    };

    struct Scheme
    {
        std::string GetSchemeType() const { return "scheme_type_42"; }
    };


    class World
    {
        // dummy members to make it compile
        // will be different things of course
        int mass, epoch;
        std::chrono::system_clock::time_point _start;

        std::string report;
      public:
        std::vector<int> cellSequence; // for demo, public

        World(std::string&, EDIN::Region&, unsigned int&, const std::chrono::system_clock::time_point&)
            : mass(99), epoch(88), _start(std::chrono::system_clock::now()), report("Report101")
        {
            // TODO!
        }

        Region       Bounds() const { return Region();       }
        int          Epoch()  const { return epoch;          }
        EDIN::Scheme Scheme() const { return EDIN::Scheme(); }
        std::chrono::system_clock::time_point StartingWallTime() const {
            return _start;
        }

        World()
            : mass(99), epoch(88), _start(std::chrono::system_clock::now()), report("Report101")
        {
        }

        void Save(char const filename[]);
        static World* Load(char const filename[]);

        private:
            friend class boost::serialization::access;
            template<class Archive> void serialize(Archive & ar, const unsigned int file_version)
            {
                ar & mass & epoch;
            }
    };
}

namespace boost
{
    namespace serialization
    {
        template<class Archive>
        inline void save_construct_data(Archive & ar, EDIN::World const * t, unsigned const int file_version)
        {
            time_t const totalRunningTime = std::chrono::duration_cast<std::chrono::duration<time_t, std::milli>>(
                std::chrono::system_clock::now() - t->StartingWallTime()).count();

            EDIN::Region const bounds = t->Bounds();
            time_t const epoch = t->Epoch();

            std::string tmp = t->Scheme().GetSchemeType();
            ar
                << bounds
                << epoch
                << totalRunningTime 
                << tmp;
        }

        template<class Archive>
        inline void load_construct_data(Archive & ar, EDIN::World * t, const unsigned int file_version)
        {
            EDIN::Region bounds;
            unsigned epoch;
            time_t totalRunningTime;
            std::string schemeType;
            ar >>
                bounds >>
                epoch >>
                totalRunningTime >>
                schemeType;

            std::chrono::system_clock::time_point const startingWallTime =
                std::chrono::system_clock::now() - std::chrono::duration<time_t, std::milli>(totalRunningTime);

            ::new(t) EDIN::World(schemeType,bounds,epoch,startingWallTime);
        }
    }
}

#include <fstream>

namespace EDIN {
    void World::Save(char const filename[])
    {
        std::ofstream ofs(filename);

        boost::archive::text_oarchive oa(ofs);

        oa << this << cellSequence << report; // line 590: error!
    }

    World* World::Load(char const filename[])
    {
        World *world = 0;

        std::ifstream ifs("world.save");
        boost::archive::text_iarchive ia(ifs);

        ia >> world;
        ia >> world->cellSequence >> world->report;

        return world;
    }
}

int main()
{
    EDIN::World world;
    world.cellSequence.push_back(12345);
    world.cellSequence.push_back(-6767);
    world.Save("world.save");

    EDIN::World* restored = EDIN::World::Load("world.save");
    restored->Save("world2.save");

    delete restored;
}

It compiles fine here using GCC 4.8.1 and Boost 1.55.0. I am using GCC 4.9.0 and boost 1.55.0.

EDIT: I have found a hack that seems to work:

The problem seems to be that G++ 4.9 doesn't want to cast World::this from World* to World*&. Replacing the World::Save method with the following resolves the problem:

void World::Save(char const filename[])
{
    std::ofstream ofs(filename);

    boost::archive::text_oarchive oa(ofs);

    World* thisCopy = this;
    oa << thisCopy << cellSequence << report;
}

There seems to be a difference in behaviour between GCC 4.8.1 and GCC 4.9 that causes the latter not to compile unless I create copy of the this pointer. If someone could point out why this is happening and whether or not it's a bug or intended change in behaviour that would be appreciated!

manlio
  • 18,345
  • 14
  • 76
  • 126
quant
  • 21,507
  • 32
  • 115
  • 211
  • @T.C. your comment and the first answer encourage **[cargo cult progamming](http://en.wikipedia.org/wiki/Cargo_cult_programming)**. You don't fix problems by randomly changing code. You'll have to understand what the library does, and how it was intended to do this, before you can suggest fixes. Just simply changing `this` to `*this` changes object tracking semantics. – sehe May 03 '14 at 09:02
  • @Arman Please, always make your sample code [SSCCE](http://www.sscce.org/). I hope my answer gets you started in the right direction. – sehe May 03 '14 at 09:04
  • @sehe I'll keep this in mind for future reference. – quant May 04 '14 at 00:42

2 Answers2

3

You are not giving all the information necessary.

Here's a self-contained example based on the sample code you /did/ show

As you can see, it runs fine. The only significant edits I think that may be causing trouble are these:

std::string tmp = t->Scheme().GetSchemeType();
ar
    << bounds
    << epoch
    << totalRunningTime 
    << tmp;

// and the following (use of this as mutable reference, which some versions of GCC erronously accepted)

void SaveWorld(World* world, char const filename[])
{
    std::ofstream ofs(filename);
    boost::archive::text_oarchive oa(ofs);
    oa << world << world->cellSequence << world->report; // line 590: error!
}

The trouble is that returns either by value, or by const&, because the t pointer is also const in this context. However, ar << requires a non-const references**[1]**, so it should not compile.

The reason MSVC might have accepted it, nonetheless, could be because MSVC has an (evil) non-standard extension that extends lifetimes of temporaries, when bound to a non-const reference.

My demo app shows serialization and deserialization in action:

int main()
{
    using namespace EDIN;
    std::unique_ptr<World> world(new World);

    world->cellSequence.push_back(12345);
    world->cellSequence.push_back(-6767);
    SaveWorld(world.get(), "world.save");

    world.reset(LoadWorld("world.save"));
    SaveWorld(world.get(), "world2.save");
}

And you can verify that world.save and world2.save end up being identical for yourself (also on coliru).

Full SSCCE code for reference:

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/valarray.hpp>
#include <chrono>
#include <memory>

namespace EDIN
{
    struct Region
    {
        int id;
        Region(int id = 42) : id(id) {}

      private:
        friend class boost::serialization::access;
        template<class Archive> void serialize(Archive & ar, const unsigned int file_version)
        {
            ar & id;
        }
    };

    struct Scheme
    {
        std::string GetSchemeType() const { return "scheme_type_42"; }
    };


    class World
    {
        // dummy members to make it compile
        // will be different things of course
        int mass, epoch;
        std::chrono::system_clock::time_point _start;

        std::string report;
      public:
        std::vector<int> cellSequence; // for demo, public

        World(std::string&, EDIN::Region&, unsigned int&, const std::chrono::system_clock::time_point&)
            : mass(99), epoch(88), _start(std::chrono::system_clock::now()), report("Report101")
        {
            // TODO!
        }

        Region       Bounds() const { return Region();       }
        int          Epoch()  const { return epoch;          }
        EDIN::Scheme Scheme() const { return EDIN::Scheme(); }
        std::chrono::system_clock::time_point StartingWallTime() const {
            return _start;
        }

        World()
            : mass(99), epoch(88), _start(std::chrono::system_clock::now()), report("Report101")
        {
        }

        friend void SaveWorld(World* world, char const filename[]);
        friend World* LoadWorld(char const filename[]);

        private:
            friend class boost::serialization::access;
            template<class Archive> void serialize(Archive & ar, const unsigned int file_version)
            {
                ar & mass & epoch;
            }
    };
}

namespace boost
{
    namespace serialization
    {
        template<class Archive>
        inline void save_construct_data(Archive & ar, EDIN::World const * t, unsigned const int file_version)
        {
            time_t const totalRunningTime = std::chrono::duration_cast<std::chrono::duration<time_t, std::milli>>(
                std::chrono::system_clock::now() - t->StartingWallTime()).count();

            EDIN::Region const bounds = t->Bounds();
            time_t const epoch = t->Epoch();

            std::string tmp = t->Scheme().GetSchemeType();
            ar
                << bounds
                << epoch
                << totalRunningTime 
                << tmp;
        }

        template<class Archive>
        inline void load_construct_data(Archive & ar, EDIN::World * t, const unsigned int file_version)
        {
            EDIN::Region bounds;
            unsigned epoch;
            time_t totalRunningTime;
            std::string schemeType;
            ar >>
                bounds >>
                epoch >>
                totalRunningTime >>
                schemeType;

            std::chrono::system_clock::time_point const startingWallTime =
                std::chrono::system_clock::now() - std::chrono::duration<time_t, std::milli>(totalRunningTime);

            ::new(t) EDIN::World(schemeType,bounds,epoch,startingWallTime);
        }
    }
}

#include <fstream>

namespace EDIN {
    void SaveWorld(World* world, char const filename[])
    {
        std::ofstream ofs(filename);

        boost::archive::text_oarchive oa(ofs);

        oa << world << world->cellSequence << world->report; // line 590: error!
    }

    World* LoadWorld(char const filename[])
    {
        World *world = 0;

        std::ifstream ifs("world.save");
        boost::archive::text_iarchive ia(ifs);

        ia >> world;
        ia >> world->cellSequence >> world->report;

        return world;
    }
}

int main()
{
    using namespace EDIN;
    std::unique_ptr<World> world(new World);

    world->cellSequence.push_back(12345);
    world->cellSequence.push_back(-6767);
    SaveWorld(world.get(), "world.save");

    world.reset(LoadWorld("world.save"));
    SaveWorld(world.get(), "world2.save");
}

[1] For obscure technical reasons, beyond the scope here

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Added deserialization for roundtrip testing and instructive value. [coliru](http://coliru.stacked-crooked.com/a/859b52aea8810def) – sehe May 03 '14 at 08:55
  • Thanks sehe, I think your answer definitely gets me in the right direction here :) – quant May 04 '14 at 00:37
  • Oh and thanks for pointing out the reason MSVC might have accepted it. – quant May 04 '14 at 00:41
  • Hmm, when I run an exact copy of your code (from here: http://coliru.stacked-crooked.com/a/859b52aea8810def) using my setup I get this error: `test.cpp:122:12: error: no match for ‘operator<<’ (operand types are ‘boost::archive::text_oarchive’ and ‘EDIN::World*’) oa << this << cellSequence << report; // line 590: error!` – quant May 04 '14 at 00:56
  • One of the errors reads `/home/armanschwarz/lib/boost_1_55_0/boost/archive/detail/interface_oarchive.hpp:62:15: note: no known conversion for argument 1 from ‘EDIN::World*’ to ‘EDIN::World*&’`. – quant May 04 '14 at 01:04
  • I've modified my question to include a copy of your SSCCE and my compiler/boost versions. Could this be to do with the fact that I'm using GCC 4.9? – quant May 04 '14 at 01:10
0

This is not so much an answer as a hack until someone proposes a real solution. The problem seems to be that G++ 4.9 doesn't want to cast World::this from World* to World*&. Replacing the World::Save method with the following resolves the problem:

void World::Save(char const filename[])
{
    std::ofstream ofs(filename);

    boost::archive::text_oarchive oa(ofs);

    World* thisCopy = this;
    oa << thisCopy << cellSequence << report;
}

There seems to be a difference in behaviour between GCC 4.8.1 and GCC 4.9 that causes the latter not to compile unless I create copy of the this pointer. If someone could point out why this is happening and whether or not it's a bug or intended change in behavior that would be appreciated!

quant
  • 21,507
  • 32
  • 115
  • 211
  • Ah. It's the same thing really. Indeed GCC accepting it was a compiler bug. You already have found the workaround. Semantically, you should really make Save a free/static function; consider the symmetry with `Load`: `Load` cannot have access to a `this` because it doesn't exist yet. Logically, the same should go for `Save`. Updated my answer. – sehe May 04 '14 at 09:11