Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does C++ prefer this template method to a method overload?

Assuming I had two classes, the first one for writing primitive types (bool, int, float, etc.) and the second one extending the first to also write complex types:

struct Writer {
    virtual void Write(int value) = 0;
};

struct ComplexWriter : public Writer {
    template <typename TValue> void Write(const TValue &value) {
        boost::any any(value);
        Write(any);
    }
    //virtual void Write(int value) = 0; // see question below
    virtual void Write(const boost::any &any) = 0;
};

The idea is that if someone calls myWriter.Write(someIntValue);, the int overload will receive priority over the templated method.

Instead, my compiler (Visual C++ 11.0 RC) always picks the template method. The following code snippet, for example, will print Wrote any to the console:

struct ComplexWriterImpl : public ComplexWriter {
    virtual void Write(int value) { std::cout << "Wrote an int"; }
    virtual void Write(const boost::any &any) { std::cout << "Wrote any"; }
};

void TestWriter(ComplexWriter &writer) {
    int x = 0;
    writer.Write(x);
}

int main() {
    ComplexWriterImpl writer;
    TestWriter(writer);
}

The behavior suddenly changes when I declare the Write(int) method in the ComplexWriter class as well (see commented out line in the first snippet). It then prints Wrote an int to the console.

Is this how my compiler ought to behave? Does the C++ standard explicitly say that only overloads defined in the same class (and not a base class) shall be prioritized over a templated method?

like image 270
Cygon Avatar asked Jun 07 '12 12:06

Cygon


People also ask

How is template function advantageous over function overloading?

Function overloading is used when multiple functions do similar operations; templates are used when multiple functions do identical operations. Templates provide an advantage when you want to perform the same action on types that can be different.

How template functions can be overloaded?

You may overload a function template either by a non-template function or by another function template. The function call f(1, 2) could match the argument types of both the template function and the non-template function.

Can template class be overloaded?

A template function can be overloaded either by a non-template function or using an ordinary function template.

What is the difference between function overloading vs function templates?

What is the difference between function overloading and templates? Both function overloading and templates are examples of polymorphism features of OOP. Function overloading is used when multiple functions do quite similar (not identical) operations, templates are used when multiple functions do identical operations.


2 Answers

The problem is that at the point you're calling writer.Write(x) the compiler sees a ComplexWriter not a ComplexWriterImpl, so it is only aware of the functions defined in ComplexWriter - the template function and the boost::any function.

ComplexWriter does not contain any virtual functions that accept an int, so it has no way to call through to the int overload defined in ComplexWriterImpl

When you add in the virtual overload to the ComplexWriter class, then the compiler becomes aware that there is an integer overload in the ComplexWriter class and therefore calls through to it's implementation in ComplexWriterImpl

EDIT: Now that you've edited in the inheritance between ComplexWriter & Writer, I've got a more complete explanation for you:

When you create a subclass and define a function in it then all of the functions of that name in the base class will be hidden, regardless of their argument types.

You can get around this with the using keyword I believe:

struct ComplexWriter : public Writer {
    template <typename TValue> void Write(const TValue &value) {
        boost::any any(value);
        Write(any);
    }
    using Writer::Write;
    virtual void Write(const boost::any &any) = 0;
};

For more details see this FAQ entry: http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.9

EDIT 2: Just to confirm that this does indeed solve your problem: http://ideone.com/LRb5a

like image 195
obmarg Avatar answered Sep 29 '22 21:09

obmarg


When you access the object via the ComplexWriter "interface", the compiler will try to resolve the function call to Write(int) using the definitions in that class. If it does not able to do so, it will consider base classes.

In this case, you have two candidates: Write(any) and the templated version. Since there is no explicit Write(int) available at this point, it will have to choose between these two options. Write(any) requires an implicit conversion, while the templated version does not, so the templated version is called (which in turn calls Write(any)).

To make the Write(int) from Writer available, import the Writer::Write functions:

class ComplexWriter : public Writer
{
  using Writer::Write;
  // rest is as before
};
like image 38
Attila Avatar answered Sep 29 '22 21:09

Attila