Roman Dryndik
Roman Dryndik

Reputation: 1184

Tricky Polymorphism and virtual functions

I have the following code.

#include <iostream> 
using namespace std; 

class K { 
public: 
    virtual void add_st(K* n) {
      cout << "add_st (K*) from K\n";
    } 
};

class L: public K { 
public: 
    virtual void add_st(L* a) {
      cout << "add_st (L*) from L\n";
    } 
}; 

int main() { 
    L ob, ob2;
    K  k, *pl = &ob; 
    pl->add_st(&ob2); 
    return 0; 
} 

The output of this program will be:

add_st (K*) from K

The reason why if I didn't miss anything is Virtual Function Table. The object is generated from the top of hierarchy down to the lowest class.

But this code:

#include <iostream> 
using namespace std; 

class K { 
public: 
    virtual void add_st() {
      cout << "add_st (K*) from K\n";
    } 
};

class L: public K { 
public: 
    virtual void add_st() {
      cout << "add_st (L*) from L\n";
    } 
}; 

int main() { 
    L ob, ob2;
    K  k, *pl = &ob; 
    pl->add_st(); 
    return 0; 
} 

Will print

add_st (L*) from L

Why?

Upvotes: 1

Views: 524

Answers (2)

Ben Voigt
Ben Voigt

Reputation: 283684

Virtual functions are invariant on the argument list, and covariant on the return type.

An elementary way to think of this is that where the virtual member function is introduced in the base class, it defines a contract.

For example, given

struct K
{ 
    virtual K* add_st(K* n);
};

the contract is that add_st accepts any object of type K (by pointer), and returns an object of type K (by pointer).

This will override it

struct L : K
{ 
    virtual K* add_st(K* a);
};

because the contract is clearly met, and so will:

struct M : K
{ 
    virtual M* add_st(K* a);
};

because the return is an object of type M, which by inheritance also is an object of type K; the contract is satisfied.

But this (the case in the question) does not override

struct N : K
{ 
    virtual K* add_st(N* a);
}; 

because it cannot accept any object of type K, only ones which are both type K and type N. And neither is this:

struct P : K
{ 
    virtual K* add_st(void* a);
};

even though from a type-theoretic perspective, contravariant parameters would be compatible, the truth is that C++ supports multiple inheritance and upcasts sometimes require pointer adjustment, so contravariant parameter types break at the implementation level.

They will create a new function (new slot in the v-table) that overloads and hides the existing function, instead of overriding it. (As John Smith says in his answer, a using-declaration can be used to avoid hiding the base version)

And the following is an error, because the signature is the same, but the return type is incompatible:

struct Q : K
{ 
    virtual void* add_st(K* a);
};

Here the result can be any object type, but that's not good enough, the contract requires an object of type K. And it can't override the existing function, because the parameters don't differ. So it is just rejected.

For more details on variance, you may want to read about the Liskov Substitution Principle.

Upvotes: 6

CS Pei
CS Pei

Reputation: 11047

First, function signature includes the function name and its parameter types. In your first example, the function name is the same but its parameter types are different. Therefore they have different signatures. Therefore in your first example, the function in your child class did not override the function in its parent.

Second there is also the concept of overload and name hiding. In your case the function definition in the first example hides its parent function. If you bring the parent function in to the same scope, the child function will overload the parent function, like this

class L: public K { 
public: 
    using K::add_st;
    virtual void add_st() {
        cout << "add_st (L*) from L\n";
}; 

Upvotes: 1

Related Questions