简体   繁体   中英

How does C++ choose which operator overload to use?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM