C Board  

Go Back   C Board > General Programming Boards > C++ Programming

Reply
 
LinkBack Thread Tools Display Modes
Old 06-17-2009, 05:07 PM   #1
Registered User
 
Jace's Avatar
 
Join Date: May 2009
Posts: 8
rvirtual and inline function with different prototypes (to enforce default parameter)

Suppose I have this base + derived class:
Code:
class B
{
public:
  virtual void Foo( int x, int mode );
};

class C : public B
{
public:
  virtual void Foo( int x, int mode );
};
Now I can do
Code:
C* bla;
bla->Foo(1,0);
Similar to the C class, I have a few hunderd other classes derived from B as well.

Now, I want to have a default value for the 'mode' parameter. But if I do this:
Code:
class B
{
public:
  virtual void Foo( int x, int mode=0 );
};
And change the function call into:
Code:
bla->Foo(1);
I get an error "C::Foo requires 2 parameters".

Ok, so I thought I could work around this by adding an inline wrapper function to the base class, which calls the actual function, like this:
Code:
class B
{
public:
  virtual void Foo( int x, int mode );
  inline void Foo( int x ) { Foo(x,0); }
};
But now, when calling bla->Foo(1) my compiler still says that C::Foo requires 2 parameters. While I obviously mean the new inlined B::Foo here.

Why is this? Is it somehow invalid or ambiguous what I'm doing here?

(PS. Workarounds: add the =0 default in all derived classes as well (lotsa work), or keep the above inline function and change the function calls into bla->B::Foo(1) or ((B*)bla)->Foo(1) (somewhat less work in my case), but I'm just wondering)
Jace is offline   Reply With Quote
Old 06-17-2009, 05:38 PM   #2
Senior software engineer
 
brewbuck's Avatar
 
Join Date: Mar 2007
Location: Portland, OR
Posts: 5,379
In your base class B:

Code:
class B
{
public:
    virtual void Foo( int x, int mode );
    virtual void Foo( int x ) { Foo( x, 0 ); }
};
Now all classes which derive from B can still override Foo( int, int ), but if somebody calls Foo( int ) it just bounces to the virtual implementation and passes the default value.

EDIT: Hmm, I see you've tried that. I don't know why it shouldn't work.

EDIT EDIT: It works if you declare Foo( int ) as virtual. I think it's starting to make sense now. The overload set has to be all virtual or all non-virtual, I think. But that was something I was not aware of.
__________________
"Congratulations on your purchase. To begin using your quantum computer, set the power switch to both off and on simultaneously." -- raftpeople@slashdot

Last edited by brewbuck; 06-17-2009 at 05:44 PM.
brewbuck is online now   Reply With Quote
Old 06-17-2009, 05:59 PM   #3
Registered User
 
Join Date: Sep 2004
Location: California
Posts: 2,845
Quote:
The overload set has to be all virtual or all non-virtual
Really? I've never heard this before.
bithub is offline   Reply With Quote
Old 06-17-2009, 06:39 PM   #4
Registered User
 
Jace's Avatar
 
Join Date: May 2009
Posts: 8
Quote:
Originally Posted by brewbuck View Post
EDIT EDIT: It works if you declare Foo( int ) as virtual. I think it's starting to make sense now. The overload set has to be all virtual or all non-virtual, I think. But that was something I was not aware of.
Thanks, but unfortunately it doesn't work here. What kind of compiler are you using? Visual Studio 8 here.
Jace is offline   Reply With Quote
Old 06-17-2009, 09:54 PM   #5
Registered User
 
Join Date: Jan 2008
Posts: 575
O_o

To much hassle... just dropping some notes and code.

If you are not overriding the behavior allow the declaration of the inherited function to be inherited. (Do not declare the function again in a derived class unless you need to do so for other reasons.)

Code:
class B
{
public:
  virtual void Foo( int x, int mode = 7)
  {
  }
};

class C : public B
{
public:
  //virtual void Foo( int x, int mode );
};

int main()
{
	C a;
	a.Foo(0);
	return(0);
}
If you need to override the behavior, use the same declared default value for ever parameter that you need.

Code:
class B
{
public:
  virtual void Foo( int x, int mode = 7)
  {
  }
};

class C : public B
{
public:
  virtual void Foo( int x, int mode = 7)
  {
  }
};

int main()
{
	C a;
	a.Foo(0);
	return(0);
}

I hate this method because the default value can get out of sync. (If the default value is out of sync you violate "is-a" and the more appropriate "constrained-as".) You can go a more robust route by changing them both at the same time by using a separate value to store the default value.

Code:
class B
{
public:
  virtual void Foo( int x, int mode = default_mode)
  {
  }
  static const int default_mode = 7;
};

class C : public B
{
public:
  virtual void Foo( int x, int mode = default_mode)
  {
  }
};

int main()
{
	C a;
	a.Foo(0);
	return(0);
}
However, this too has problems in a header only distribution. (This I believe is fixed in "C++0X".) You may instead use a function with the short interface forwarding to the implementation of the long interface. (Exactly as you've done, you just need to do it appropriately.)

As with the other path, you should not use a declaration in the derived class unless you need to override the behavior.

Code:
class B
{
public:
  virtual void Foo( int x)
  {
	  this->Foo(x, 7);
  }
  virtual void Foo( int x, int mode)
  {
  }
};

class C : public B
{
public:
  //virtual void Foo( int x, int mode);
};

int main()
{
	C a;
	a.Foo(0);
	return(0);
}
If you do need to override the behavior, the implementation, you must tell the compiler that the both functions, the short interface and the implementation interface, are available to the derived class.

You should be careful with this path because it is easy to fall into the trap of only declaring the inherited short interface function. (I've included it for the sake of completeness. It is the following example.) This approach will fail to compile for various reasons.

Code:
class B
{
public:
  virtual void Foo( int x)
  {
	  this->Foo(x, 7);
  }
  virtual void Foo( int x, int mode)
  {
  }
};

class C : public B
{
public:
  virtual void Foo(int x);
  virtual void Foo( int x, int mode)
  {
  }
};

int main()
{
	C a;
	a.Foo(0);
	return(0);
}
On failing with that you will probably implement the forwarding functions in the derived class--the same as you've done in the base class.

Code:
class B
{
public:
  virtual void Foo( int x)
  {
	  this->Foo(x, 7);
  }
  virtual void Foo( int x, int mode)
  {
  }
};

class C : public B
{
public:
  virtual void Foo(int x)
  {
     this->Foo(x, 7);
  }
  virtual void Foo( int x, int mode)
  {
  }
};

int main()
{
	C a;
	a.Foo(0);
	return(0);
}
This approach is fine in some cases. (Which is why I've included the approach.) But it can be tedious and error prone, and with multiple derived classes the overhead becomes a serious problem.

In most cases you will only tell the compiler to use the functions from the derived class with a using declaration.

Code:
class B
{
public:
  virtual void Foo( int x)
  {
	  this->Foo(x, 7);
  }
  virtual void Foo( int x, int mode)
  {
  }
};

class C : public B
{
public:
  using B::Foo;
  virtual void Foo( int x, int mode)
  {
  }
};

int main()
{
	C a;
	a.Foo(0);
	return(0);
}
Just for the sake of information, both approaches will violate the requirements and standard practices of many code houses so you should have a good reason to require the behavior--a reason beyond "I don't want to have to remember the correct value.".

Soma
phantomotap is offline   Reply With Quote
Old 06-18-2009, 02:23 AM   #6
Registered User
 
Jace's Avatar
 
Join Date: May 2009
Posts: 8
Thanks for your suggestions.

Of course I'm only re-declaring the function in a derived class if I actually need to override its implementation there.

The problem is I have over hundred classes like C which derive from B and have to override that function. But I think I'll stick to the method of defining the default value in the baseclass, and reuse the same default wherever I override it. Except for a one time effort (probably a smart regexp search & replace can do the trick), this seems to offer the least hassle and the best maintainability.

Except to tackle the header-only distribution issue, I'll use an ENUM instead:
Code:
class B
{
public:
  enum { default_mode = 17; }
  virtual void Foo( int x, int mode = default_mode );  
};

class C : public B
{
public:
  virtual void Foo( int x, int mode = default_mode );
};

C *a;
a->Foo(0);
This seems to work fine
Jace is offline   Reply With Quote
Old 06-18-2009, 03:01 AM   #7
The larch
 
Join Date: May 2006
Posts: 3,082
Code:
class B
{
public:
  enum { default_mode = 17 };
  virtual void Foo( int x, int mode ) {}
  void Foo(int x) { Foo(x, default_mode); }
};

class C : public B
{
public:
  virtual void Foo( int x, int mode ) {}
};

int main()
{
    B *a = new C;
    a->Foo(0);
}
This seems to compile fine with MingW and Comeau online. Is there any truth in that all overloads must be virtual or non-virtual?
__________________
I might be wrong.

Quote:
Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
Quoted more than 1000 times (I hope).
anon is offline   Reply With Quote
Old 06-18-2009, 03:56 AM   #8
Registered User
 
Jace's Avatar
 
Join Date: May 2009
Posts: 8
Quote:
Originally Posted by anon View Post
B *a = new C;
Yeah but a is now a B*, not a C*
Jace is offline   Reply With Quote
Old 06-18-2009, 04:20 AM   #9
Registered User
 
Join Date: Jun 2005
Posts: 1,343
Quote:
Originally Posted by anon View Post
Code:
class B
{
public:
  enum { default_mode = 17 };
  virtual void Foo( int x, int mode ) {}
  void Foo(int x) { Foo(x, default_mode); }
};

class C : public B
{
public:
  virtual void Foo( int x, int mode ) {}
};

int main()
{
    B *a = new C;
    a->Foo(0);
}
This seems to compile fine with MingW and Comeau online.
Problem is, it will fail for this;
Code:
int main()
{
    C *c = new C;
    c->Foo(0);
}
due to the hiding rule (i.e. C::Foo(int, int) hides B::Foo(int)).

Quote:
Originally Posted by anon View Post
Is there any truth in that all overloads must be virtual or non-virtual?
No there is not. The concern is that interactions with the hiding rule must be correctly taken into account.

One common way to avoid unwanted consequences of the hiding rule in this case is to use a function of a different name. Accordingly, I would do this;
Code:
class B
{
public:
  void Foo( int x, int mode = 7);   // assuming default value of mode is 7
protected:
  virtual void FooImplementation(int x, int mode);
};

void B::Foo(int x, int mode)
{
    FooImplementation(x, mode);  // this is a polymorphic call
}

class C : public B
{
protected:
  virtual void FooImplementation( int x, int mode );
};
__________________
Right 98% of the time, and don't care about the other 3%.
grumpy is offline   Reply With Quote
Old 06-18-2009, 04:58 AM   #10
Registered User
 
Jace's Avatar
 
Join Date: May 2009
Posts: 8
Quote:
Originally Posted by grumpy View Post
One common way to avoid unwanted consequences of the hiding rule in this case is to use a function of a different name. Accordingly, I would do this;

(..FooImplementation..)
Aight, that's probably the cleanest way. Thing is I already have lots of "Foo" calls and derived implementations, but still I can prolly get that fixed with some S&R.
Thanks!
Jace is offline   Reply With Quote
Reply

Tags
default parameter, function, inline, virtual

Thread Tools
Display Modes

Forum Jump


All times are GMT -6. The time now is 07:02 PM.


Powered by vBulletin® Version 3.8.1
Copyright ©2000 - 2009, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.3.0 RC2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22