I'm writing a set of classes for custom serialization and deserialization. I figured I would use the << and >> operators since they convey that meaning generally. Let's start with a class for handling writing to a generic stream.
class Writer
{
public:
virtual void writeBytes(const void* p, size_t n) = 0;
template <typename T> void write(const T& v)
{
writeBytes(&v, sizeof(v));
}
template <typename T> Writer& operator<<(const T& v)
{
write(v);
return *this;
}
};
Then there's an interface for something that's serializable, ie that provides it's own method for serialization.
class Serializable
{
public:
virtual Writer& serialize(Writer& writer) const = 0;
};
Writer& operator<<(Writer& writer, const Serializable& s)
{
s.serialize(writer);
return writer;
}
Finally, I wrote an example of how to use it: a serializable buffer.
class SerializableBuffer : public Serializable
{
public:
SerializableBuffer() : data_(NULL), length_(0) { }
SerializableBuffer(void* data, size_t length) : data_(data), length_(length) { }
virtual Writer& serialize(Writer& writer) const
{
writer.writeBytes(data_, length_);
return writer;
}
private:
void* data_;
size_t length_;
};
So here's the interesting part. Obviously, if I use method calls, it does exactly what it's supposed to. But using the << operator is showing some quirks. My first attempt is to do the following:
unsigned char input[] = { 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0x12, 0x34 };
SerializableBuffer sb(input, sizeof(input));
unsigned char d[8];
BufferWriter writer(buffer(d));
writer << sb;
This fails, because the output buffer isn't big enough. If I add a printf, it turns out that it's calling the template in the Writer class! Here's the weirder part, the following works.
unsigned char input[] = { 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0x12, 0x34 };
SerializableBuffer sb(buffer(input));
unsigned char d[8];
BufferWriter writer(buffer(d));
writer << (Serializable&)sb;
I'm guessing the templating engine is winning over a descendent? Can anyone explain what's going on here and why?
Its pretty simple -- its just following the overload resolution rules. You have two overloaded operator <<
that can possibly match when the first argument is a Writer &
or a subclass:
template <typename T> Writer& Writer::operator<<(const T& v);
Writer& operator<<(Writer& writer, const Serializable& s);
When the second argument is a SerializableBuffer
, the first can match exactly, while the second can match with a conversion. Since matching exactly is better, the first matches.
When the second argument is a Serializable
, both match exactly, so the second is better as it is not a template.
If you want to make the template not match when the argument is a subclass of Serializable
, you can use enable_if
:
template <typename T>
std::enable_if<!std::is_base_of<Serializable, T>::value, Writer &>::type
operator<<(const T &v)
which will cause the template to not be instantiatable for any subclass of Serializable
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.