cereal icon indicating copy to clipboard operation
cereal copied to clipboard

New feature: Loading and returning object (no default constructor involved)

Open jbriales opened this issue 3 years ago • 5 comments

Context and motivation

There is this very common and recurring pattern when using Cereal to load an object

Foo foo;
{
  // ...
  iar(foo);
}

which is aimed at loading an object Foo from a serialized archive. However, this forces having a default constructor for Foo.

Having classes with no default constructor is a common pattern associated to different idioms, like "named constructors". Often enough in different libraries, the default-constructor is deleted to ensure only sensible instances of a type can be created. This ensures things are correct at construction time, and reduces the number of runtime checks required to avoid bugs coming from unexpected uses of invalid values (the usual alternative of having a default constructor with dummy values).

As of now though, all available interfaces in Cereal's Archives, i.e. iar(foo), iar >> foo, ... expect Foo to exist before loading.

This has been (and still is) a continuous source of frictions for Cereal users, as shown by related issues like this, this, this, this, this, this, ...

The current workaround for this involves using smart pointers, which you can define in advance (empty) and populate by constructing in-place later, aligned with the current typical Cereal interface for loading.

This assumption on having default constructors for serialized types spreads over the library and is present in e.g. pair-like containers (pair_associative_container.hpp), std::optional (in optional.hpp), std::vector, ...

Because of all the above, currently Cereal users are often forced to either:

  • Introducing default constructors for classes that were NOT intended to have one (private or public).
  • Introducing smart pointers where they are not necessary/expected by design, just as a workaround.

Proposal

After looking at the internal details of the library, I saw it should be doable to extend Cereal's Archive interface in a clean way that removes the need to have a default constructor or any other workarounds. This would be done by exposing a load method that loads and returns an object of a certain type, i.e. sth like

// ...
cereal::BinaryInputArchive iar(in);
Foo foo = iar.load_and_return<Foo>();

vs

Foo foo;
{
  // ...
  cereal::BinaryInputArchive iar(in);
  iar(foo);
}

The proposed fix would not break any existing use-case, BUT however it opens up Cereal to work with a myriad of use cases involving non-default-constructible types by using the new additional load_and_return interface.

I have a working prototype of this, via defining an alternative load_and_return member or non-member method of a given class Foo, and some proof-of-concept tests.

Before I move forward and do a more comprehensive testing and implementation for PR, I'd like to hear what the Cereal community thinks about this.

jbriales avatar Aug 31 '20 18:08 jbriales

Anyone any opinion in here? I'm not sure about the current status of the library, but I see there is a nr of pending pull requests and if the library is not under active development, I'd like to know before investing any effort here. From the lack of answers or interest to the proposal I understand the library is not very actively maintained?

jbriales avatar Sep 12 '20 16:09 jbriales

Hi! The feature you suggesting would be a very appreciated and useful thing! I vote for this with both hands as also having troubles with requirement that types must be default constructible to work nice with Cereal.

BTW how are you going to implement this? Most probably you'll have to introduce new serialization function or somehow wire it into existing load_and_construct()? What's your plan? Update: reread your post and see that you're going to add new load function. Nice.

uentity avatar Sep 12 '20 17:09 uentity

You're right that library isn't actively maintained and it's a pity, because the overall design is not bad and from my point of view Cereal is the best existing replacement for boost::serialization with MUCH simpler and more modern implementation (though I'm not aware of current boost::serialization progress).

I think most of people that needs some enhancements just maintain their own Cereal forks (like I do).

uentity avatar Sep 12 '20 17:09 uentity

Thanks for your answer @uentity! As you said, it's a pity a good library with (AFAICT) a wide set of users stops being actively maintained, specially when there is people willing to contribute (as the many open PR and forks indicate!). I wonder if the main developers have made any statement on this sense. But if they feel they cannot handle the maintenance burden well, maybe they could open it up and call up for contributors that might be up for the task of more actively maintaining and tracking PRs and issues?

jbriales avatar Sep 15 '20 17:09 jbriales

@jbriales: I really like your idea, and as @uentity already suspected, I also have written my own version of this feature when I needed it (but never had the time to do the appropriate polishing and unit testing, especially since my project which uses cereal already has a lot of test a level higher. And I am still not sure I used the std::launder correctly...).

If you want to compare your approach with mine (which sound really not that different), mine is stored under https://github.com/USCiLab/cereal/compare/master...serpedon:containersNotDefaultConstructableTypes

The main different as far as I can get is that you introduced a new function load_and_return where I just extended the cereal::load_and_construct-interface which is already used in the context of pointers. For me it sounds like there is a one-to-one mapping between the two functions: cereal::load_and_construct<T>(iar, "elementName") vs. iar.load_and_return<Foo>("elementName")

The main effort seems to be to adapt all the containers to whichever approach is used.

serpedon avatar Dec 19 '20 19:12 serpedon