Thread: rvirtual and inline function with different prototypes (to enforce default parameter)

  1. #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)

  2. #2
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    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.
    Last edited by brewbuck; 06-17-2009 at 05:44 PM.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  3. #3
    Registered User
    Join Date
    Sep 2004
    Location
    California
    Posts
    3,268
    The overload set has to be all virtual or all non-virtual
    Really? I've never heard this before.

  4. #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.

  5. #5
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    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

  6. #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

  7. #7
    The larch
    Join Date
    May 2006
    Posts
    3,573
    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.

    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).

  8. #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*

  9. #9
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    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%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  10. #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!

Popular pages Recent additions subscribe to a feed

Tags for this Thread