Boost logo

Boost Users :

Subject: Re: [Boost-users] [Serialization] STL Containers and Non-Default Constructors
From: Trevor Thoele (lordimmortal2013_at_[hidden])
Date: 2015-05-23 21:07:50


Bjorn Reese wrote
> I think boost/serialization/list.hpp checks for the default constructor
> in the wrong way. If you use std::vector instead of std::list, then it
> compiles.

Unfortunately this isn't exactly true. I had it using an std::vector before
using std::list, which I ultimately sent and it fails as well. I have
checked std::list, std::vector, std::forward_list, std::unordered_map,
std::map, and std::unordered_set.

Robert Ramey wrote
> I've recently spent some time on this and I believe that I've now fixed
> it. I had some difficulty with "is_default_constructible".

Unfortunately this isn't where the problem is coming from, at least not on
MSVC++2013.

The problem with std::vector and std::list (and the other sequential,
non-associative containers) is that regardless of is_default_contructible<T>
with T being non-default constructible, the compiler will generate code that
calls T::T() and will have a compiler error because of it. Specifically, you
call std::vector::resize, std::list::resize and std::forward_list::resize
with the second argument being default which results in the generation of
code calling T::T().

Inside boost/serialization/list.hpp and the load function:

if(detail::is_default_constructible<U>()){
        t.resize(count);
/////////////////////////////////////////////////////// <- here
        typename std::list<U, Allocator>::iterator hint;
        hint = t.begin();
        while(count-- > 0){
            ar >> boost::serialization::make_nvp("item", *hint++);
        }
    }

The problem is when U is non-default constructible. That if will never stop
the code generation under it even if is_default_constructible<U> will always
be false. std::list::resize has the signature "void resize (size_type n,
value_type val = value_type())"; this is where the compiler error originates
from, at least for std::list, std::vector and std::forward_list.

The best solution I thought of would be to change the basic if to a tag
dispatch method, or to utilize SFINAE, to preclude the compiler from
generating code calling U::U().

std::vector also has an additional layer above it using tag dispatch to
select between unoptimized and optimized algorithms. The above will work for
both of those algorithms (the problem is the same, IE they're both calling
resize).

The problem for the associative containers is a bit different, but
(potentially?) easier to fix.

Inside of boost\serialization\unordered_map.hpp:

struct archive_input_unordered_map
{
    inline void operator()(
        Archive &ar,
        Container &s,
        const unsigned int v
    ){
        typedef typename Container::value_type type;
        detail::stack_construct<Archive, type> t(ar, v);
//////////////////////////////////// <- here
        // borland fails silently w/o full namespace
        ar >> boost::serialization::make_nvp("item", t.reference());
        std::pair<typename Container::const_iterator, bool> result =
            s.insert(t.reference());
        // note: the following presumes that the map::value_type was NOT
tracked
        // in the archive. This is the usual case, but here there is no way
        // to determine that.
        if(result.second){
            ar.reset_object_address(
                & (result.first->second),
                & t.reference().second
            );
        }
    }
};

Type = std::unordered_map::value_type, which is an std::pair<Key, Value>.
detail::stack_construct calls boost::serialization::load_construct_data_adl
and because you're giving it an std::pair type, it'll call std::pair's
load_contruct_data; std::pair does not have a load_construct_data, so it
calls the default constructor - that default constructor in turn calls the
default constructors for both elements it watches over, and in this case one
of those types is non-default constructible.

The solution I came up with to fix this was to not serialize the std::pair's
and instead just serialize the two elements directly. This has fixed this
problem and I am now able to serialize non-default constructible types in an
std::unordered_map. The code below is the new loader:

template<class Archive, class Container>
            struct archive_input_unordered_map
            {
                inline void operator()(Archive &ar, Container &s, const
unsigned int v)
                {
                    typedef typename Container::key_type First;
                    typedef typename Container::mapped_type Second;
                    // borland fails silently w/o full namespace
                    detail::stack_construct<Archive, First> first(ar, v);
                    ar >> boost::serialization::make_nvp("first",
first.reference());
                    detail::stack_construct<Archive, Second> second(ar, v);
                    ar >> boost::serialization::make_nvp("second",
second.reference());
                    std::pair<typename Container::const_iterator, bool>
result = s.emplace(std::move(first.reference()),
std::move(second.reference()));
                    // note: the following presumes that the map::value_type
was NOT tracked
                    // in the archive. This is the usual case, but here
there is no way
                    // to determine that.
                    if(result.second)
                        ar.reset_object_address(&(result.first->second),
&second.reference());
                }
            };

One would also have to change the saving code to reflect the new loading
above.

I'm not sure if that works for all cases. It might not be savory because of
the elimination of the item nvp for XML. I also had to add in the
std::move's to work with std::unique_ptr's, not sure if your usage of insert
would differ from my usage of emplace to impact that in any way, though;
obviously these would need more testing.

--
View this message in context: http://boost.2283326.n4.nabble.com/Serialization-STL-Containers-and-Non-Default-Constructors-tp4675790p4676023.html
Sent from the Boost - Users mailing list archive at Nabble.com.

Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net