3

This question refers to common problems discussed in these questions:

Can virtual functions have default parameters?

Virtual functions default parameters

Here is what currently happens in c++ with default parameters to virtual functions:

struct Base
{
    virtual void foo(int one = 1, int two = 2)
            { cout << "one: " << one << " two: " << two << endl; }
};

struct Derived : public Base
{
    virtual void foo(int one = 3, int two = 4) 
        { Base::foo(one, two); cout << " derived!" << endl; }
};

int main()
{
    Base* b = new Base();
    Base* d = new Derived();

    Derived* dp = new Derived();

   b->foo();
   d->foo();
   dp->foo();

   return 0;
}

output:

one: 1 two: 2
one: 1 two: 2
 derived!
one: 3 two: 4
 derived!

This is the behaviour I wish existed in c++ virtual function default parameters:

#include <iostream>

using namespace std;

struct Base
{
    virtual void foo () { foo(1, 2); }
    virtual void foo (int one) { foo(one, 2); }
    virtual void foo(int one, int two)
            { cout << "one: " << one << " two: " << two << endl; }
};

struct Derived : public Base
{   
    virtual void foo() { foo(3, 4); }
    virtual void foo(int one, int two) 
        { Base::foo(one, two); cout << " derived!" << endl; }
};

int main()
{
    Base* b = new Base();
    Base* d = new Derived();

    Derived* dp = new Derived();

   b->foo();
   d->foo();
   dp->foo();

   return 0;
}

output:

one: 1 two: 2
one: 3 two: 4
 derived!
one: 3 two: 4
 derived!

So, basically, if I want to override a default parameter in a parent class, I will just create a new foo with that number of arguments. Note that the derived overrides the no-arg condition, but not the one-arg condition. As a side note, my current project uses a purely virtual base class. I often have pointers of the base class type and of the derived class type. I want a call from either pointer to have the same result.

QUESTIONS:

I have read many questions related to this subject, but they all don't seem to give reasonable solutions. Most of the solutions would result in uglier code all throughout your project.

Some say "Don't use default parameters on virtual functions," but then I would need to put the default in every place I call the function. It seems it would be better to just put a comment that says, "also change base class" and "also change derived class" than to have to change everywhere the function is called.

Some say to only have the default parameters in the base class, but that means that any pointer to a derived object would need to be cast back to a base pointer before the defaults could be used. That makes a lot of code ugly as well.

Are there reasons I should avoid the above design?

Community
  • 1
  • 1
Cory-G
  • 1,025
  • 14
  • 26
  • I don't see a default parameter here. Could you add it so we know what you mean exactly? – gexicide Aug 05 '14 at 19:06
  • @gexidide The default parameters are faked using overloading see `foo()`, `foo(one)`, and `foo(one, two)` in the `Base` class and `foo()` and `foo(one, two)` in the `Derived` class – Cory-G Aug 05 '14 at 19:09
  • But then, they are not default parameters. Thus, the claim "Don't use default parameters on virtual functions" no longer holds, as the thing you do here is **not** using default parameters. What does not work with your solution? Can you give a case where the wrong result is produced? – gexicide Aug 05 '14 at 19:10
  • @gexicide This question refers to a common "problem". I realize that many may not know of this discussion. See the links I added to the beginning for the full context. – Cory-G Aug 05 '14 at 19:14
  • All the links talk about real default parameters, not faked ones. Faked ones - in contrast to real ones - if applied correctly do work in a polymorphic scenario, so I don't see a problem with your design. – gexicide Aug 05 '14 at 19:15
  • Are you conflating "default constructor" with "default parameter?" – John Dibling Aug 05 '14 at 19:41
  • @JohnDibling No. To see the problem with my proposed solution more clearly, please see link 2 above, answer 1. – Cory-G Aug 05 '14 at 19:49
  • 1
    @CoryBeutler: I'm just having a hard time understanding what you're trying to achieve. You mention default parameters, but there are no default parameters mentioned anywhere in your code. – John Dibling Aug 05 '14 at 19:51
  • Are you saying that you want to call `d->foo()` and have `foo(3,4)` actually happen? – John Dibling Aug 05 '14 at 19:52
  • If you could post the desired `cout` output, it might help. – John Dibling Aug 05 '14 at 19:53
  • @JohnDibling I have edited the answer to include current workings of c++ and my desired output. – Cory-G Aug 05 '14 at 20:00
  • Please, pretty please, add a *rationale* why on earth you want different default parameters in derived classes. Ask yourself if you're still modelling is-a relationship. – Martin Ba Aug 05 '14 at 20:01

3 Answers3

3

At some point in time future maintainers of your code will be baffled, confused, and/or perplexed if you change the default values depending on which static type they call foo on so I'm going to assume that's not your concern.

Given that your concern is someone changing the default in the parent and forgetting to update the child class that's easily solved with the non-virtual interface pattern:

#include <iostream>

using namespace std;

struct Base
{
    void foo(int one = 1, int two = 2) { foo_impl(one, two); }

protected:
    virtual void foo_impl(int one, int two)
    { cout << "one: " << one << " two: " << two << endl; }
};

struct Derived : public Base
{   
protected:
    virtual void foo_impl(int one, int two)
    { Base::foo_impl(one, two); cout << " derived!" << endl; }
};

int main()
{
    Base* b = new Base();
    Base* d = new Derived();

    Derived* dp = new Derived();

    b->foo();
    d->foo();
    dp->foo();

    return 0;
}
Mark B
  • 95,107
  • 10
  • 109
  • 188
  • This solution would be good in many cases, but does not have the same result as the "desired result" code in my question. I would like the derived classes to be able to override the default parameters and use those no matter the calling methods of foo. Since my current project has a purely virtual base class, I wouldn't mind if the defaults only resided in the derived classes, and not the base class at all (essentially make `foo()` and `foo(one)` in the question ` = 0`.) I will up-vote, though, since this may help some answer-seekers. – Cory-G Aug 05 '14 at 19:42
  • 1
    @Cory Beutler Overriding the default parameters in specific child classes will only confuse users and other maintainers of your code. Just don't do it. You spend an extra five seconds of typing now to explicitly pass your parameters and save five hours of debugging six months from now when someone doesn't realize the defaults are different for each level of inheritance. – Mark B Aug 05 '14 at 19:51
  • If I have to explicitly pass those parameters every time I call the function, then those defaults change, I would have to find every call in the code and change it in every case, that sounds worse to me. – Cory-G Aug 05 '14 at 19:55
  • @Cory Beutler Agreed, and a single well-specified set of default values is perfectly fine and maintainable. Can you give us the concrete use case where different child classes need different defaults? Maybe there's an entirely different mindset to solve your root problem. – Mark B Aug 05 '14 at 20:02
  • I will try, but it *may* need to be posted in a separate question as this one is now quite long and may have different answers. I will post a link here if I do post it as a separate question. – Cory-G Aug 05 '14 at 20:23
3

I think the problem in "understanding what's going on" is that default values for function arguments are resolved at compile-time - this means that unless it's VERY obvious and simple, the compiler will not KNOW what class some pointer is pointing at, and won't give the right argument. In other words, the compiler will use the class of the pointer when determining the arguments for your function. There is absolutely no way you can work around this (other than "knowing which class you want to use", but then it's pretty meaningless to use virtual functions).

The solution depends on what you REALLY want to do, but an obvious answer is to NOT use default arguments. The other solution is, like you have, some sort of indirect function call, where the "no arguments" function is different from the one and two argument functions.

But it's perhaps also a good idea to wonder if your design is right in the first place. Maybe you need to find a different solution somewhere...

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • Just remembered sth from my professor in C++ class, function parameters along with function names are compiled into an unique string/signature, so my understand is default parameter values are decided at compile time, and compiler won't know what class the pointer it's pointing to yet. – strisunshine May 18 '17 at 19:58
  • Indeed, the compiler won't know which function gets called until after it has compiled the code, so won't be able to use default values in the "correct" way here. – Mats Petersson May 19 '17 at 06:24
0

First of all, you are not using default parameters in your example. Thus, the problem that usual default parameters have do not apply to your code. In your code, subclasses can override default parameters which will work as expected no matter if a Derived is used through a Base or a Derived pointer. Thus, I do not see a problem with your approach, it seems fine to me.

gexicide
  • 38,535
  • 21
  • 92
  • 152