Can virtual functions have default parameters?

C++C++11C++03

C++ Problem Overview


If I declare a base class (or interface class) and specify a default value for one or more of its parameters, do the derived classes have to specify the same defaults and if not, which defaults will manifest in the derived classes?

Addendum: I'm also interested in how this may be handled across different compilers and any input on "recommended" practice in this scenario.

C++ Solutions


Solution 1 - C++

Virtuals may have defaults. The defaults in the base class are not inherited by derived classes.

Which default is used -- ie, the base class' or a derived class' -- is determined by the static type used to make the call to the function. If you call through a base class object, pointer or reference, the default denoted in the base class is used. Conversely, if you call through a derived class object, pointer or reference the defaults denoted in the derived class are used. There is an example below the Standard quotation that demonstrates this.

Some compilers may do something different, but this is what the C++03 and C++11 Standards say:

> 8.3.6.10: > --------- > > > > 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. An > overriding function in a derived > class does not acquire default arguments from the function it > overrides. Example:

> struct A { > virtual void f(int a = 7); > }; > struct B : public A { > void f(int a); > }; > void m() > { > B* pb = new B; > A* pa = pb; > pa->f(); //OK, calls pa->B::f(7) > pb->f(); //error: wrong number of arguments for B::f() > }


Here is a sample program to demonstrate what defaults are picked up. I'm using structs here rather than classes simply for brevity -- class and struct are exactly the same in almost every way except default visibility.

#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>

using std::stringstream;
using std::string;
using std::cout;
using std::endl;

struct Base { virtual string Speak(int n = 42); };
struct Der : public Base { string Speak(int n = 84); };

string Base::Speak(int n) 
{ 
	stringstream ss;
	ss << "Base " << n;
	return ss.str();
}

string Der::Speak(int n)
{
	stringstream ss;
	ss << "Der " << n;
	return ss.str();
}

int main()
{
	Base b1;
	Der d1;

	Base *pb1 = &b1, *pb2 = &d1;
	Der *pd1 = &d1;
	cout << pb1->Speak() << "\n"	// Base 42
		<< pb2->Speak() << "\n"		// Der 42
		<< pd1->Speak() << "\n"		// Der 84
		<< endl;
}

The output of this program (on MSVC10 and GCC 4.4) is:

Base 42
Der 42
Der 84

Solution 2 - C++

This was the topic of one of Herb Sutter's early Guru of the Week posts.

The first thing he says on the subject is DON'T DO THAT.

In more detail, yes, you can specify different default parameters. They won't work the same way as the virtual functions. A virtual function is called on the dynamic type of the object, while the default parameter values are based on the static type.

Given

class A {
    virtual void foo(int i = 1) { cout << "A::foo" << i << endl; }
};
class B: public A {
    virtual void foo(int i = 2) { cout << "B::foo" << i << endl; }
};
void test() {
A a;
B b;
A* ap = &b;
a.foo();
b.foo();
ap->foo();
}

you should get A::foo1 B::foo2 B::foo1

Solution 3 - C++

This is a bad idea, because the default arguments you get will depend on the static type of the object, whereas the virtual function dispatched to will depend on the dynamic type.

That is to say, when you call a function with default arguments, the default arguments are substituted at compile time, regardless of whether the function is virtual or not.

@cppcoder offered the following example in his [closed] question:

struct A {
    virtual void display(int i = 5) { std::cout << "Base::" << i << "\n"; }
};
struct B : public A {
    virtual void display(int i = 9) override { std::cout << "Derived::" << i << "\n"; }
};

int main()
{
    A * a = new B();
    a->display();

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

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

Which produces the following output:

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

With the aid of the explanation above, it is easy to see why. At compile time, the compiler substitutes the default arguments from the member functions of the static types of the pointers, making the main function equivalent to the following:

    A * a = new B();
    a->display(5);

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

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

Solution 4 - C++

As other answers have detailed, its bad idea. However since no one mentions simple and effective solution, here it is: Convert your parameters to struct and then you can have default values to struct members!

So instead of,

//bad idea
virtual method1(int x = 0, int y = 0, int z = 0)

do this,

//good idea
struct Param1 {
  int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)

Solution 5 - C++

As you can see from the other answers this is a complicated subject. Instead of trying to do this or understand what it does (if you have to ask now, the maintainer will have to ask or look it up a year from now).

Instead, create a public non-virtual function in the base class with default parameters. Then it calls a private or protected virtual function that has no default parameters and is overridden in child classes as needed. Then you don't have to worry about the particulars of how it would work and the code is very obvious.

Solution 6 - C++

This is one that you can probably figure out reasonably well by testing (i.e., it's a sufficiently mainstream part of the language that most compilers almost certainly get it right and unless you see differences between compilers, their output can be considered pretty well authoritative).

#include <iostream>

struct base { 
    virtual void x(int a=0) { std::cout << a; }
    virtual ~base() {}
};

struct derived1 : base { 
    void x(int a) { std:: cout << a; }
};

struct derived2 : base { 
    void x(int a = 1) { std::cout << a; }
};

int main() { 
    base *b[3];
    b[0] = new base;
    b[1] = new derived1;
    b[2] = new derived2;

    for (int i=0; i<3; i++) {
        b[i]->x();
        delete b[i];
    }

    derived1 d;
    // d.x();       // won't compile.
    derived2 d2;
    d2.x();
    return 0;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionArnold SpenceView Question on Stackoverflow
Solution 1 - C++John DiblingView Answer on Stackoverflow
Solution 2 - C++David ThornleyView Answer on Stackoverflow
Solution 3 - C++OktalistView Answer on Stackoverflow
Solution 4 - C++Shital ShahView Answer on Stackoverflow
Solution 5 - C++Mark BView Answer on Stackoverflow
Solution 6 - C++Jerry CoffinView Answer on Stackoverflow