cereal icon indicating copy to clipboard operation
cereal copied to clipboard

How to specify class version of a templated class?

Open sweeneychris opened this issue 9 years ago • 12 comments

Is this possible to do? I cannot find any documentation on it, nor an example. My class is very simple:

template <int N>
struct Prior {
 public:
  bool is_set = false;
  double value[N];

 private:
  friend class cereal::access;
  template <class Archive>
  void serialize(Archive& ar, const std::uint32_t version) {
    ar(is_set, value);
  }
};

CEREAL_CLASS_VERSION(Prior, 1);

This will not compile with the latest cereal release. Is there a way to specify to cereal that the class is templated so that I can use the class version?

sweeneychris avatar Jul 27 '16 19:07 sweeneychris

Prior is not a class

nabijaczleweli avatar Jul 27 '16 19:07 nabijaczleweli

Struct and Class are nearly equivalent in c++ other than public vs private defaults (and inheritence).

Changing this to a class does not work and gives exactly the same errors.

sweeneychris avatar Jul 27 '16 19:07 sweeneychris

If you wanna get really technical, then, Prior is neither a class nor a struct

nabijaczleweli avatar Jul 27 '16 19:07 nabijaczleweli

Unfortunately, CEREAL_CLASS_VERSION needs a full type, which Prior is not (it's only a template definition). You can get this to work by manually defining CEREAL_CLASS_VERSION(Prior<1>, 1); /* ... */ CEREAL_CLASS_VERSION(Prior<99>, 1);, etc but I doubt that's very helpful for you.

The easiest solution would be to look at the boost preprocessor library to loop over as many N as you think you'll need with a macro and define the above.

You might be able to find some template trickery to define a cereal::detail::Version that is further templated on your N, but I can't see how to do that immediately.

randvoorhies avatar Jul 27 '16 19:07 randvoorhies

As has been said you won't be able to use `CEREAL_CLASS_VERSION' directly, but you can do it manually and probably make your own macro to make this convenient.

See the following example:

template <class T>
struct A
{
  T x;
  template <class Archive>
  void serialize( Archive & ar, const std::uint32_t version )
  {
    ar( x );
  }
};

namespace cereal { namespace detail {
  template <class T> struct Version<A<T>>
  {
    static const std::uint32_t version;
    static std::uint32_t registerVersion()
    {
      ::cereal::detail::StaticObject<Versions>::getInstance().mapping.emplace(
           std::type_index(typeid(A<T>)).hash_code(), 3 );
      return 3;
    }
    static void unused() { (void)version; }
  }; /* end Version */
  template <class T>
  const std::uint32_t Version<A<T>>::version =
    Version<A<T>>::registerVersion();
} } // end namespaces

The above gives a version of 3 to any instantiation of A<class T>.

AzothAmmo avatar Jul 27 '16 19:07 AzothAmmo

@randvoorhies and @AzothAmmo Thanks for the helpful responses! That is more or less what I figured but good to confirm it. And doubly thanks for providing a workaround

sweeneychris avatar Jul 27 '16 19:07 sweeneychris

@AzothAmmo Have you tried this? Does the Version<A<T>>::version actually get instantiated to trigger the registerVersion call when the compilation unit is loaded? I didn't think the compiler would actually do that for a templated version

randvoorhies avatar Jul 27 '16 20:07 randvoorhies

Seemed to work in a really simple test case:

int main()
{
  {
    cereal::JSONOutputArchive ar( std::cout );
    A<int> a = {3};
    A<double> b = {3.14};
    ar( a, b );
  }
}
{
    "value0": {
        "cereal_class_version": 3,
        "value0": 3
    },
    "value1": {
        "cereal_class_version": 3,
        "value0": 3.14
    }
}

AzothAmmo avatar Jul 27 '16 20:07 AzothAmmo

That's amazing! Just tested it with the Prior class, and it works great.

randvoorhies avatar Jul 27 '16 20:07 randvoorhies

I would suggest that a macro (or whatever is appropriate) for this gets incorporated into the main repo if possible. Seems pretty useful!

sweeneychris avatar Jul 28 '16 22:07 sweeneychris

I just created my own macro based on the CEREAL_CLASS_VERSION macro, with some "CEREAL_UNPACK" magic courtesy of this post to support multiple template arguments

#include <cereal/cereal.hpp>

#define CEREAL_UNPACK(...) __VA_ARGS__

#define CEREAL_TEMPLATE_CLASS_VERSION(ARGS, TYPE, VERSION_NUMBER)                         \
namespace cereal { namespace detail {                                                     \
    template<CEREAL_UNPACK ARGS> struct Version<CEREAL_UNPACK TYPE> {                     \
        static std::uint32_t registerVersion()                                            \
        {                                                                                 \
            ::cereal::detail::StaticObject<Versions>::getInstance().mapping.emplace(      \
                std::type_index(typeid(CEREAL_UNPACK TYPE)).hash_code(),                  \
                                CEREAL_UNPACK VERSION_NUMBER);                            \
            return CEREAL_UNPACK VERSION_NUMBER;                                          \
        }                                                                                 \
        static inline const std::uint32_t version = registerVersion();                    \
        CEREAL_UNUSED_FUNCTION                                                            \
    }; /* end Version */                                                                  \
} }

You can use it like this:

CEREAL_TEMPLATE_CLASS_VERSION((typename KEY, typename VALUE), (std::map<KEY, VALUE>), (123));

Note the parenthesis to make CEREAL_UNPACK work!

Edit: added support for multiple template arguments Edit2: moved namespace into macro, added template arguments as ARGS macro parameter, VERSION_NUMBER now supports templates with multiple args, too (e.g. for Foo<X, Y>::Version)

lubensky avatar Apr 18 '23 11:04 lubensky

maybe the following is another workaround:

class B {
    int m_a;
    double m_b;
    static constexpr std::uint32_t sm_version = 1;

    friend class cereal::access;
    void serialize(auto &ar)
    {
        // a workaround for templated class version
        auto version = sm_version;
        ar(version);
        if (version == 0) {
              ar(m_a);
        } else if (version == sm_version) {
              ar(m_a, m_b);
        }
    }
};

xiaodaxia-2008 avatar Jul 25 '24 13:07 xiaodaxia-2008