Resolution of virtual function with default parameters [duplicate]
Asked Answered
D

3

6

header.h

#include <iostream>
using namespace std;
class A
{
    public:
        virtual void display(int i=5) { cout<< "Base::" << i << endl; }
};

class B : public A
{
    public:
        void display(int i=9) { cout<< "Derived::" << i << endl; }
};  

source.h

#include <iostream>
#include "header.h"
using namespace std;
int main()
{
    A * a = new B();
    a->display();

    A* aa = new A();
    aa->display();

    B* bb = new B();
    bb->display();
}  

output

Derived::5
Base::5
Derived::9  

My understanding was default parameter functions were resolved during compile time using function overloading. Virtual functions were then resolved during runtime using function overriding.

But what is happening is a mess.
How does the function resolution actually happen here?

Dispart answered 23/9, 2014 at 12:0 Comment(2)
Default argument resolution is based on the static type of the object through which you call the function (i.e. based on the pointer type). social.msdn.microsoft.com/Forums/en-US/…Logician
Note that you can work around this problem by overloading the virtual function with another virtual function without the paramter, which "forwards" to the one with the parameter. Like: in Base virtual void display() { display(9); } and in Derived void display() { display(5); }Trenatrenail
D
8

Your code is actually seen by the compiler like this:
(The display() method is not actually there, but the resolving works in similar way)

class A
{
public:
    virtual void display(int i) { cout<< "Base::" << i << endl; }
    void display() { display(5); }
};

class B : public A
{
public:
    void display(int i) override { cout<< "Derived::" << i << endl; }
    void display() { display(9); }
};

Now you should understand what happens. You are calling the non-virtual display() which calls the virtual function. In more strict words: the default argument is resolved just like if the no-arg non-virtual method was there - by the type of the variable (not by the actual type of the object), but the code gets executed according to real object type, because it is virtual method:

int main()
{
    A * a = new B(); // type of a is A*   real type is B
    a->display();    // calls A::display() which calls B::display(5)

    A* aa = new A(); // type of aa is A*  real type is A
    aa->display();   // calls A::display() which calls A::display(5)

    B* bb = new B(); // type of bb is B*  real type is B
    bb->display();   // calls B::display() which calls B::display(9)
}  
Downstream answered 23/9, 2014 at 12:9 Comment(8)
Oh, this is exactly what I was looking for.Hey, but then how is Derived::5 printed? It should be Base::5Dispart
When you invoke a->display(); on an object of (dynamic) type B, it calls display();, that is the non-virtual overload display() which calls display(5); which is then virtually resolved to B::display(5). And in the original code something similar happens. Instead of the additional non-virtual function, it is the default parameter which is resolved on the static type.Trenatrenail
Hmm, this is complicated.Dispart
Note that firda's code is only a demonstration for better understanding what happens under the hood. The compiler does not really translate your code to this one.Trenatrenail
@Trenatrenail I think, then it is a different question to ask how functions with default parameters are resolved?Dispart
Yeah but I'm sure it exists. I recommend you to read the linked question (duplicate).Trenatrenail
I read it. But that didn't say how the functions gets resolved.Dispart
@cppcoder: see the answer from Andreas. I have placed non-virtual display() on both classes taking the default argument to mimic how it works, how it is resolved.Downstream
F
8

There is no polymorphism on default arguments. They are resolved compile-time.

A::display has default argument equal 5. B::display has default argument equal 9. It's only the type of a, aa, bb variables that matters.

Use of different default arguments in polymorphic methods is confusing and should be avoided.

Freely answered 23/9, 2014 at 12:8 Comment(2)
I'm sorry but I don't understand: why a->display() is showing Derived::5 and bb->display() is showing Derived::9? They are defined in exactly the same way...Lattice
@StefanoF Because a is of type A* while bb is of type B*. Hence, a->display() uses the declaration from class A while bb->display() uses the one from class BLogician
D
8

Your code is actually seen by the compiler like this:
(The display() method is not actually there, but the resolving works in similar way)

class A
{
public:
    virtual void display(int i) { cout<< "Base::" << i << endl; }
    void display() { display(5); }
};

class B : public A
{
public:
    void display(int i) override { cout<< "Derived::" << i << endl; }
    void display() { display(9); }
};

Now you should understand what happens. You are calling the non-virtual display() which calls the virtual function. In more strict words: the default argument is resolved just like if the no-arg non-virtual method was there - by the type of the variable (not by the actual type of the object), but the code gets executed according to real object type, because it is virtual method:

int main()
{
    A * a = new B(); // type of a is A*   real type is B
    a->display();    // calls A::display() which calls B::display(5)

    A* aa = new A(); // type of aa is A*  real type is A
    aa->display();   // calls A::display() which calls A::display(5)

    B* bb = new B(); // type of bb is B*  real type is B
    bb->display();   // calls B::display() which calls B::display(9)
}  
Downstream answered 23/9, 2014 at 12:9 Comment(8)
Oh, this is exactly what I was looking for.Hey, but then how is Derived::5 printed? It should be Base::5Dispart
When you invoke a->display(); on an object of (dynamic) type B, it calls display();, that is the non-virtual overload display() which calls display(5); which is then virtually resolved to B::display(5). And in the original code something similar happens. Instead of the additional non-virtual function, it is the default parameter which is resolved on the static type.Trenatrenail
Hmm, this is complicated.Dispart
Note that firda's code is only a demonstration for better understanding what happens under the hood. The compiler does not really translate your code to this one.Trenatrenail
@Trenatrenail I think, then it is a different question to ask how functions with default parameters are resolved?Dispart
Yeah but I'm sure it exists. I recommend you to read the linked question (duplicate).Trenatrenail
I read it. But that didn't say how the functions gets resolved.Dispart
@cppcoder: see the answer from Andreas. I have placed non-virtual display() on both classes taking the default argument to mimic how it works, how it is resolved.Downstream
L
6

This behaviour is specified in Chapter 8.3.6: Default arguments within Programming languages — C++ (ISO/IEC 14882:2003(E)) :

A virtual function call (10.3) uses the default arguments in the declaration of the virtual function determined by the static type of the pointer or reference denoting the object

Logician answered 23/9, 2014 at 12:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.