I have an ISerializable interface with pure virtual read and write functions. I also have a List<T> class. So, my question is: can I inherit my List<T> from ISerializable only if T is also inherit from ISerializable?
usage example:
#include <array>
#include <iostream>
class ISerializable
{
public:
virtual void read(std::istream& stream) = 0;
virtual void write(std::ostream& stream) = 0;
};
class A : public ISerializable {
public:
void read(std::istream& stream) override {}
void write(std::ostream& stream) override {}
};
class B {};
template<typename T>
class List : public ISerializable
{
public:
void read(std::istream& stream) override
{
for (int i = 0; i < items.size(); i )
{
items[i].read(stream);
}
}
void write(std::ostream& stream) override
{
for (int i = 0; i < items.size(); i )
{
items[i].write(stream);
}
}
private:
std::array<T, 10> items;
};
int main()
{
List<A> list_a;
//List<B> list_b; <- error here
}
CodePudding user response:
You can indeed set up something like that up in C 20.
template<typename T>
class List : public ListCommon<T> {
};
template<std::derived_from<ISerializable> S>
class List<S> : public ListCommon<S>, public ISerializable {
// Implement the serialization
};
The specialization for a serialziable type is only viable with the added constraint of the type being derived from ISerializable (checked with the standard std::derived_from concept).
ListCommon is where I would place the common implementation details to avoid repeating them upon specialization.
CodePudding user response:
Ok, this solution fits if you already have a lot of classes that you don't really want to refactor and/or duplicate. The disadvantage here is that IDE will not be able to assist you sometimes. However, since it implies one of main features of concepts, it is still legal. Just be careful!
Here we have an interface that explicitly requires some functions to be implemented and a concept that also requires same functions to be implemented. Prefer interface over the concept! If you inherit interface, IDE will assist you in function overloading. Concept is needed if you want to use interface as a parameter type later (see utility class example). This is an example of duck typing.
class ISerializable
{
public:
virtual void write_to_stream(std::ostream& stream) const = 0;
virtual void read_from_stream(std::istream& stream) = 0;
};
template<typename T>
concept ImplicitlySerializable = requires(const T a1, T a2, std::ostream os, std::istream is)
{
a1.write_to_stream(os);
a2.read_from_stream(is);
};
template<typename T>
concept Serializable = std::derived_from<T, ISerializable> || ImplicitlySerializable<T>;
Some utility functions to handle any object that match concept
class StreamUtils
{
public:
template<Serializable T>
static void write(std::ostream& stream, const T& value)
{
value.write_to_stream(stream);
}
template<Serializable T>
static void read(std::istream& stream, T& to)
{
to.read_from_stream(stream);
}
};
Usage example:
// Interface usage
class Animal : public ISerializable
{
void write_to_stream(std::ostream& stream) override
{
// serialization code here
}
void read_from_stream(std::istream& stream) override
{
// deserialization code here
}
}
// Concept usage
template<typename T>
class List
{
void write_to_stream(std::ostream& stream) const requires Serializable<T>
{
// serialization code here
}
void read_from_stream(std::istream& stream) requires Serializable<T>
{
// deserialization code here
}
{
int main()
{
std::ofstream file("my_file.bin", std::ios::out | std::ios:binary);
StreamUtils::write(file, Animal());
StreamUtils::write(file, List<Animal>());
}
