How to invert templates dependency?

96 views Asked by At

In case I want to make a generic class template ResourceManager which serializes its content calling serialize function (or template), how to make call to serialize below compilable?

I don't want to declare a function template for vectors, because it is assumed that the project will use ResourceManager with different types of containers and I don't want to declare all possible containers or include this file in the middle of my sources after such declaration.

Can I somehow avoid this declaration or inverse the dependencies so that would work with std containers as well as with my types?

In case I worked only with my types, I would make serialize their class member, but I want to work with std containers, as well without wrapping them.

#include <fstream>
#include <iostream>
#include <vector>

// With this forward declaration it works for `std::vector`
// template <class T>
// void serialize(std::ofstream& ofs, std::vector<T>& v);

template <class Resource>
struct ResourceManager {
    Resource& resourse;

    ResourceManager(Resource& resourse) : resourse(resourse) {}

    void store() {
        std::ofstream ofs("vector_bin.vec", std::ios::trunc | std::ios::binary);

        serialize(ofs, resourse);
    }
};

template <class T>
void serialize(std::ofstream& ofs, std::vector<T>& v) {
    auto size = v.size();
    ofs.write((const char*)&size, sizeof(size));

    ofs.write((const char*)v.data(), v.size() * sizeof(int));
}

int main()
{
    std::vector<int> v = { 1,2,3 };

    ResourceManager rm(v);

    rm.store();
}

Here is the live demo.

2

There are 2 answers

3
Eugene On BEST ANSWER

You can replace serialize() function with a trait class, and then partially specialize the trait for each container:

template <class Resource>
struct Serializer
{
    static void write(std::ofstream& ofs, Resource& v) = delete;
};

template <class Resource>
struct ResourceManager {
    Resource& resourse;

    ResourceManager(Resource& resourse) : resourse(resourse) {}

    void store() {
        std::ofstream ofs("vector_bin.vec", std::ios::trunc | std::ios::binary);

        Serializer<Resource>::write(ofs, resourse);
    }
};

template <class T>
struct Serializer<std::vector<T>>
{
    static void write(std::ofstream& ofs, std::vector<T>& v) {
        auto size = v.size();
        ofs.write((const char*)&size, sizeof(size));

        ofs.write((const char*)v.data(), v.size() * sizeof(int));
    }
};
6
Pepijn Kramer On

I would generalize the serialization function like this. Eeven if I think serializing like this is too fragile for practical use. It has too much of a "But it works on my machine" quality level.

#include <vector>
#include <iostream>
#include <type_traits>
#include <sstream>


// types_t... because vector can accept multiple template arguments (type and allocator)
// template<typename... types_t> typename vector_t, represents std::vector<int,allocator_type>
template <template<typename... types_t> typename vector_t, typename... types_t>
constexpr bool is_vector = std::is_same_v<std::vector<types_t...>,vector_t<types_t...>>;

// Use SFINAE to disable function for anything other than std::vector<int,allocator>
// also generalize to ALL output streams (not only files)
template <template<typename... types_t> typename vector_t, typename... types_t>
auto serialize(std::ostream& os, const vector_t<types_t...>& v) -> std::enable_if_t<is_vector<vector_t,types_t...>,std::ostream&>
{
    auto size = v.size();
    os.write(reinterpret_cast<const char*>(&size), sizeof(size));

    // in your original code you assumed the vector would contain an int only
    auto data_size = v.size() * sizeof(vector_t<types_t...>::value_type);
    os.write(reinterpret_cast<const char*>(v.data()), data_size);
    return os;
}

int main()
{
    std::vector<int> values{1,2,3,4};

    static_assert(is_vector<std::vector, int>);

    std::ostringstream oss;

    // Note that this kind of serialization is VERY VERY fragile
    // it makes assumptions on compiler settings (padding/alignment), endianness of the platform, 
    // architecture of the platform (8/16/32/64 bits). 
    // So in general just use a serialization library! (e.g. flatbuffers or protbuf)
    serialize(oss,values);
   
    

}