WaterFox
WaterFox

Reputation: 1106

serialization of a nested unordered_map with custom key using boost

I wrote a class with a member that is a nested unordered_map with a custom type as key and another unordered_map as value.

I would like to serialize/deserialize the inner map using the boost::serialization library.

However, even if the code is mostly done, the compiler returns this error code (I cut it down and simplified it a bit):

class std::unordered_map<
  const MyClass<...>::key_type,
  unsigned int,
  MyClass<...>::key_hash,
  std::equal_to<const MyClass<...>::key_type>,
  std::allocator<std::pair<const MyClass<...>::key_type, unsigned int> > >’ has no member named ‘serialize’

From what I understand, it tells me that there is no member serialize for this map. However, I included the boost headers that should fix this (but it is not).

I suspect that the fact I declared the key as being const in the map declaration messes with the library, but I'm unable (aka not knowledgeable enough) to make sure of it: when I remove the const qualifier I end up with a bunch of other compilation errors.

The code for my class is the following:

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/unordered_map.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <unordered_map>

template<typename A, typename B, typename C>
class MyClass
{
  public:
  using coord_type = another_serializable_and_tested_class;
  
  struct key_type
  {
    coord_type from;
    coord_type to;
    key_type(coord_type const& origin, coord_type const& destination):
    from(origin),
    to(destination)
    {}
    bool operator==(key_type const& other) const
    {
      return (
               other.from == this->from &&
               other.to == this->to
             );
    }
    
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & from;
        ar & to;
    }
  }; // end struct key_type

  struct key_hash : public std::unary_function<key_type, std::size_t>
  {
   std::size_t operator()(const key_type& k) const
   {
     size_t res = 17;
     res = res * 31 + std::hash<coord_type>()( k.from );
     res = res * 31 + std::hash<coord_type>()( k.to );
     return res;
   }
  }; // end struct key_hash
  
  using forward_flow_type = std::unordered_map<time_type, std::unordered_map<const key_type, value_type, key_hash>>;

  // other members and functions, including the serialization/deserialization operations:
  void deserialize_layer(int t) const
  {
    const std::string forward_filename = get_forward_flow_archive_name(t);
    // create and open an archive for input
    std::ifstream forward_ifs(forward_filename, std::ios::binary);
    boost::archive::binary_iarchive forward_ia(forward_ifs);
    // read class state from archive
    std::unordered_map<const key_type, value_type, key_hash> forward_layer;
    forward_ia >> forward_layer;
    // archive and stream closed when destructors are called
    m_forward_flow.emplace(t,forward_layer);
  }
};

Any idea what is going wrong?

Upvotes: 2

Views: 371

Answers (1)

sehe
sehe

Reputation: 392921

Do yourself a favor and unkludge your types:

using Layer = std::unordered_map<key_type, value_type, key_hash>;
using forward_flow_type = std::unordered_map<time_type, Layer>;

The const was misguided, that's up to the container implementation.

Next,

void deserialize_layer(int t) const

is marked const, yet you ...

m_forward_flow.emplace(t, forward_layer);

That's not going to compile. So, drop the const.

The remaining issue is that your key type is not default constructible. The easiest way is to just add that:

key_type(coord_type const& origin      = {},
         coord_type const& destination = {})
    : from(origin)
    , to(destination)
{
}

The complicated way is to implement serialization using save_construct_data and load_construct_data.

Demo

This adds just enough machinery to do a roundtrip of a single frame filled with demo data:

Live On Coliru

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/functional/hash.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/unordered_map.hpp>
#include <boost/serialization/utility.hpp>
#include <fstream>
#include <iostream>
#include <unordered_map>

auto get_forward_flow_archive_name(int t)
{
    return "forward_flow_" + std::to_string(t) + ".dat";
}

struct another_serializable_and_tested_class {
    double x, y;
    void serialize(auto& ar, unsigned) { ar & x & y; }

    bool operator==(another_serializable_and_tested_class const&) const = default;

    friend size_t hash_value(another_serializable_and_tested_class const& o) {
        auto h = boost::hash_value(o.x);
        boost::hash_combine(h, boost::hash_value(o.y));
        return h;
    }

    friend std::ostream& operator<<(std::ostream& os, another_serializable_and_tested_class const& p)
    {
        return os << "(" << p.x << ", " << p.y << ")";
    }
};

template <typename A, typename B, typename C> class MyClass {
  public:
    using coord_type = another_serializable_and_tested_class;

    struct key_type {
        coord_type from;
        coord_type to;

        key_type(coord_type const& origin      = {},
                 coord_type const& destination = {})
            : from(origin)
            , to(destination)
        {
        }

        bool operator==(key_type const& other) const
        {
            return (other.from == this->from && other.to == this->to);
        }

        friend class boost::serialization::access;
        template <class Archive>
        void serialize(Archive& ar, const unsigned int version)
        {
            ar& from;
            ar& to;
        }
    }; // end struct key_type

    struct key_hash {
        std::size_t operator()(const key_type& k) const
        {
            size_t res = 17;
            boost::hash_combine(res, k.from);
            boost::hash_combine(res, k.to);
            return res;
        }
    }; // end struct key_hash

    using time_type = int; // stub
    using value_type = long; // stub

    using Layer = std::unordered_map<key_type, value_type, key_hash>;
    using forward_flow_type = std::unordered_map<time_type, Layer>;

    forward_flow_type m_forward_flow;

    // other members and functions, including the serialization/deserialization
    // operations:
    void serialize_layer(int t) const
    {
        const std::string forward_filename = get_forward_flow_archive_name(t);
        std::ofstream forward_ofs(forward_filename, std::ios::binary);
        boost::archive::binary_oarchive forward_oa(forward_ofs);
        forward_oa << m_forward_flow.at(t);
    }

    // other members and functions, including the serialization/deserialization
    // operations:
    void deserialize_layer(int t)
    {
        const std::string forward_filename = get_forward_flow_archive_name(t);
        // create and open an archive for input
        std::ifstream forward_ifs(forward_filename, std::ios::binary);
        boost::archive::binary_iarchive forward_ia(forward_ifs);
        // read class state from archive
        Layer forward_layer;
        forward_ia >> forward_layer;
        // archive and stream closed when destructors are called
        m_forward_flow.emplace(t, forward_layer);
    }
};

int main() { 
    using Object = MyClass<void, void, void>;
    {
        Object obj;

        auto& layer42 = obj.m_forward_flow.emplace(42, Object::Layer{}).first->second;

        for (auto& [k, v] : {
                std::tuple(
                        Object::key_type{
                        {1, 2}, // from
                        {3, 4}  // to
                        },
                        5),
                std::tuple(Object::key_type{{6, 7}, {8, 9}}, 10),
                std::tuple(Object::key_type{{11, 12}, {13, 14}}, 15),
                std::tuple(Object::key_type{{16, 17}, {18, 19}}, 20),
                std::tuple(Object::key_type{{21, 22}, {23, 24}}, 25),
                std::tuple(Object::key_type{{26, 27}, {28, 29}}, 30),
                })
        {
            layer42.emplace(k, v);
        }

        obj.serialize_layer(42);
    }
    {
        Object obj;
        obj.deserialize_layer(42);

        for (auto& [k, v] : obj.m_forward_flow.at(42)) {
            auto& [from,to] = k;
            std::cout << "From " << from << " to " << to << " value " << v << "\n";
        }
    }
}

Prints

From (1, 2) to (3, 4) value 5
From (6, 7) to (8, 9) value 10
From (11, 12) to (13, 14) value 15
From (16, 17) to (18, 19) value 20
From (21, 22) to (23, 24) value 25
From (26, 27) to (28, 29) value 30

as expected. The file (on my machine) contains:

00000000: 1600 0000 0000 0000 7365 7269 616c 697a  ........serializ
00000010: 6174 696f 6e3a 3a61 7263 6869 7665 0f00  ation::archive..
00000020: 0408 0408 0100 0000 0000 0000 0006 0000  ................
00000030: 0000 0000 000d 0000 0000 0000 0000 0000  ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000050: 0000 0000 0000 3a40 0000 0000 0000 3b40  ......:@......;@
00000060: 0000 0000 0000 3c40 0000 0000 0000 3d40  ......<@......=@
00000070: 1e00 0000 0000 0000 0000 0000 0000 3540  ..............5@
00000080: 0000 0000 0000 3640 0000 0000 0000 3740  [email protected]@
00000090: 0000 0000 0000 3840 1900 0000 0000 0000  ......8@........
000000a0: 0000 0000 0000 3040 0000 0000 0000 3140  [email protected]@
000000b0: 0000 0000 0000 3240 0000 0000 0000 3340  [email protected]@
000000c0: 1400 0000 0000 0000 0000 0000 0000 2640  ..............&@
000000d0: 0000 0000 0000 2840 0000 0000 0000 2a40  ......(@......*@
000000e0: 0000 0000 0000 2c40 0f00 0000 0000 0000  ......,@........
000000f0: 0000 0000 0000 1840 0000 0000 0000 1c40  .......@.......@
00000100: 0000 0000 0000 2040 0000 0000 0000 2240  ...... @......"@
00000110: 0a00 0000 0000 0000 0000 0000 0000 f03f  ...............?
00000120: 0000 0000 0000 0040 0000 0000 0000 0840  .......@.......@
00000130: 0000 0000 0000 1040 0500 0000 0000 0000  .......@........

Upvotes: 1

Related Questions