Search code examples
c++inheritancevirtualoverloadingassignment-operator

Virtual assignment operator overloading- how the correct overloaded function is chosen?


The following code (from C++ FAQs 24.11) is implementing virtual assignment operator overloading and overriding:

#include <iostream>
using namespace std;

class B{
public:
    virtual ~B() throw();
    virtual B& operator= (const B& b) throw();
};

B::~B() throw(){}

B& B::operator= (const B& b) throw()
{ cout << "B::operator=(const B&)\n"; return *this; }


class D : public B{
public:
    virtual D& operator= (const B& b) throw();
    D& operator= (const D& d) throw();
};

D& D::operator= (const B& b) throw()
{cout << "D::operator= (const B&)\n"; return *this;}

D& D::operator= (const D& d) throw()
{cout << "D::operator= (const D&)\n"; return *this;}


void sample(D& d, B& b, D& d2, B& b2){
    cout << "d = d2:   "; d = d2;
    cout << "d = b2:   "; d = b2;
    cout << "b = b2:   "; b = b2;
    cout << "b = d2:   "; b = d2;
}


int main()
{
    D d, b, d2, b2;
    sample(d,b,d2,b2);
}

and it says the output is:

  • d = d2: D::operator=(const D&)
  • d = b2: D::operator=(const B&)
  • b = b2: D::operator=(const B&)
  • b = d2: D::operator=(const B&)

It says:

"Because the compiler resolves which override to call based on the static type of the parameters, the first assignment is the only one that calls the assignment operator that takes a D; all the others end up calling the assignment operator that takes a B."

and

"The last two calls resolve to the override (D::operator= (const B&)) because the actual class of b in sample() is D. If b had actually been a B, the last two calls would have resolve to (B::operator= (const B&))"

I'm a bit confused, the first paragraph says the compiler looks at the parameter static type to determine which (overloaded?) function call is used, so why does the last case call the operator for the B parameter type, when the argument passed, d2, is declared as type D& d2 in sample()?

EDIT Referring to the answer below, I do not see how the request for B::=(D) can result in D::=(B). What if there was another subclass, E? Why would D::=(B) get called and not E::=(B)? Are we saying if B does not have a function '=' for parameter (D) then the run-time starts looking at whether any derived objects contain such a signature?


Solution

  • Overload resolution is done using the static types. Once the overload is picked, the dynamic type is used to determine which overridden version of the function is used. Let me explain each result:

    //Every object is a D object, so in
    //each case, we expect the virtual calls
    //to dispatch to the D version.
    
    //lhs: D, rhs: D, so we expect
    //D::operator=(D const&)
    cout << "d = d2:   "; d = d2;
    
    //lhs: D, rhs: B, so we expect
    //D::operator=(B const&)
    cout << "d = b2:   "; d = b2;
    
    //lhs: B, rhs: B, so we expect
    //B::operator=(B const&).
    //This is virtual, and has been overridden
    //in D, so the D version gets called.
    cout << "b = b2:   "; b = b2;
    
    //lhs: B, rhs: D, so we expect
    //B::operator(D const&).
    //This does not exist, so the closest match is used:
    //B::operator(B const&). (This is a legal match, 
    //because D is a subclass of B).
    //This is virtual, and has been overridden
    //in D, so the D version gets called.
    cout << "b = d2:   "; b = d2;