Thread: Interesting virtual function problem.

  1. #1
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708

    Interesting virtual function problem.

    I can't believe I never ran into this problem before.

    Code:
    struct first
    {
     virtual int function(int a, int b)
    {
     /* whatever */
    }
     virtual int function(int a)
    {
     return function(a, 0);
    }
     virtual int function(void)
    {
     return function(0);
    }
    };
    
    
    
    
    
    struct second : first
    {
     void stuff()
    {
     /* whatever */
    }
     virtual int function(int a, int b)
    {
     stuff();
     return first::function(a, b);
    }
    };
    
    
    
    
    
    
    int main()
    {
     second obj;
     return obj.function();
    }


    At this point, the compiler complains that 'struct second' has no function with that signature, because I overided one of the functions having the same name. Stupid. Any compiler worth it's salt should be able to sort that out!

    Anyway, so what's to do? Redefine each function? Then I came up with a work-around, though it's a bit of a hack, and demands a new discipline altogether.

    The function with the most parameters is the 'lowest common denominator' (LCD) (since all others ultimately call that one internally). So here's what I did:





    Code:
    struct first
    {
     virtual int lcd_function(int a, int b) 
    {
     /* whatever */
    }
     virtual int function(int a, int b)
    {
     return lcd_function(a, b);
    }
     virtual int function(int a)
    {
     return function(a, 0);
    }
     virtual int function(void)
    {
     return function(0);
    }
    };
    
    
    
    
    
    struct second : first
    {
     void stuff()
    {
     /* whatever */
    }
     virtual int lcd_function(int a, int b)
    {
     stuff();
    
     return first::lcd_function(a, b);
    }
    };
    
    
    
    
    
    
    int main()
    {
     second obj;
     return obj.function();
    }


    Voila.

    OK, so if anyone can come up with a better solution - please let me know. I'm glad it works, on the one hand, but disappointed that I may have to incorporate this feature into future code!

    Any suggestions are welcome.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  2. #2
    ¡Amo fútbol!
    Join Date
    Dec 2001
    Posts
    2,138
    How about:

    return obj.first::function();


    Works fine on mingw/Dev-C++.

  3. #3
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Son of a jackal, you're absolutely right.

    Still, it's annoying that it isn't automatically handled by the compiler, and it's and ugly function call - have to admit. Of course, it's a hell of a lot better than my 'solution' anyway.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  4. #4
    Registered User
    Join Date
    May 2003
    Posts
    1,619

    Re: Interesting virtual function problem.

    Originally posted by Sebastiani
    At this point, the compiler complains that 'struct second' has no function with that signature, because I overided one of the functions having the same name. Stupid. Any compiler worth it's salt should be able to sort that out!
    It's not the compiler's fault; this is the way name resolution works in C++; making it work in any other means would be more problematic. The most restricted scope is searched first, then the next, etc. Once a match is found for the name, even a match that is incorrect in the context of the statement, no further scopes are checked. This is a Good Thing; there could be some very nasty problems if C++ compilers tried to use definitions that fit better but were in a more distant scope.

    For an example of how C++ resolves, say I had this:

    Code:
    namespace a{
      namespace b{
        class A{
        /*...*/
        };
    
        class B : public A{
          void something(){
            x(4);
          }
        }
      }
    }
    When that line "x(4)" is being parsed, the compiler must resolve the name "x" into a variable/function name. So it starts at the most restrictive scope and works its way out.

    Assuming something() doesn't have any further nested scopes, it would first check to see if x was declared in something(). After that, it would search the class B, then the class A, then the namespace b, then the namespace a, then the global namespace.

    Whenever it finds at least one match, it goes no further. It looks at all the definitions of x at that level, and chooses the best of those.

    In your case, there is a name match within class second. "first" never participates in the name resolution, because at least one match is found in "second", which is a more qualified namespace than "first".

    However, you can fix your struct to get the effect you want. C++ provides a method to treat one or more names from one scope as if they were in a different scope. It's often, IMO, abused, but this is one of those great times to use it (pun intended). The change you need is as follows:

    Code:
    struct second : first
    {
        void stuff()
        {
            /* whatever */
        }
        using first::function;
        virtual int function(int a, int b)
        {
            stuff();
            return first::function(a, b);
        }
    };
    This makes all of a's copies of "function" participate in the name resolution.

    Do note, this is "all or nothing" -- you get all of first's copies of function(), or none of them. You can't get first::function(void) but not first::function(int). This would be incorrect anyway; name resolution happens before signature matching, so signatures don't affect name resolution.
    Last edited by Cat; 09-01-2003 at 01:38 AM.

  5. #5
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Hmm. Nice solution. Unfortunately, my stupid compiler doesn't like it.

    >> It's not the compiler's fault;

    I disagree. The resolution wouldn't be complex, it would just require a few more steps.

    Thanks for the reply, though. Learn something new every day.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  6. #6
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    The standard doesn't do things that way because it's *easier*, it does it that way because it's *better*, and it's consistent with the way all name resolution happens. The name is resolved the same way *regardless* of the context in which the name appears. The language as a whole would be vastly more error-prone and difficult to use if compilers didn't do name resolution this way.

    For example, this statement:

    x = ???;

    will always refer to the same x, no matter what ??? is. It doesn't matter if ??? is an object, an integer, a double, etc. Obviously some of those might give errors, but it would never, for example, choose x to mean second::x in some cases, and first::x in others. In the same way, it cannot and SHOULD not choose function() to mean second::function() in some cases, and first::function() in others, unless you specifically tell it that this is permissible. The many drawbacks of it automatically doing that far outweigh the savings of typing a few more characters.

    The above solution will work on any standard compiler -- if your compiler can't handle it, you need a better compiler.

  7. #7
    Registered User Dante Shamest's Avatar
    Join Date
    Apr 2003
    Posts
    970
    Java seems to do the exact opposite from C++. Which I think is good, because regardless of scope, an object derived from a base class has an is-a relationship with the base class, and should have its methods and should be able to overload those methods.

    Java:
    Code:
    class First
    {
     int function()
     {
        return 0 ;  
     }  
    }
    
    class Second extends First
    {
     int function( int a, int b )
     {
        return a+b ;  
     }   
     
     public static void main(String[] args)
     {
      Second d = new Second() ;
      System.out.println( "d.function() = " + d.function() ); 
     } 
    }
    The Java version compiles and runs fine.

    C++:
    Code:
    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    class First
    {
    public:
      virtual int function()
      {
        return 0 ;
      }
    };
    
    class Second : public First
    {
    public:
      virtual int function(int a, int b)
      {
        return a+b ;
      }
    };
    
    int main(int argc, char *argv[])
    {
      Second d ;
      cout << "d.function = " << d.function() ;
      system("PAUSE");	
      return 0;
    }
    The C++ version on the other hand doesn't compile because of name resolution.
    Last edited by Dante Shamest; 09-01-2003 at 08:02 PM.

  8. #8
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    The standard doesn't do things that way because it's *easier*, it does it that way because it's *better*, and it's consistent with the way all name resolution happens. The name is resolved the same way *regardless* of the context in which the name appears. The language as a whole would be vastly more error-prone and difficult to use if compilers didn't do name resolution this way.
    You're missing the point. Look: If struct second has no
    function(void) defined, then the *next* obvious step is to see what it's base class has available. Seems logical to me. After all, had it not defined a function with that name, that's exactly what would have happened.

    Anyway, just because it is the standard does not make it superior. In my opinion, this feature is a flawed one. That doesn't make me wrong mind you!!

    >> The above solution will work on any standard compiler -- if your compiler can't handle it, you need a better compiler

    I have plenty of compilers/assemblers, but only one that will generate Windows programs with C++ code - Bloodshed Software's Dev-C++. The others are:

    lcc : Windows, C
    djgpp: DOS, C & C++
    gcc: DOS, C & C++
    nasm: DOS, ASM
    masm: Windows & DOS, ASM
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  9. #9
    Registered User Dante Shamest's Avatar
    Join Date
    Apr 2003
    Posts
    970
    In my opinion, this feature is a flawed one.
    From my experience in Java, I have to agree. In C++, the idea that a single redefinition in a derived class should hide all base-class methods of the same name is ridiculous to me.

  10. #10
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Originally posted by Sebastiani
    Look: If struct second has no
    function(void) defined, then the *next* obvious step is to see what it's base class has available.
    But the point is, second *does* define the name "function". When the compiler is resolving that name, it doesn't care *at all* about type. It doesn't even care if "function" is a variable or a method (and you can't tell from syntax, either, "something();" is also legal for an object named "something" -- this is NOT true with Java).

    The signature plays no role in name resolution, just as type plays no role. This prevents you from accidently calling a function other than the one you desire.

    Seems logical to me. :confused: After all, had it not defined a function with that name, that's exactly what would have happened.
    And if you had a variable named x in the base, you could use derived::x to refer to it until you put any variable named x into derived.

    Bottom line is that the C++ committee has felt that name resolution should be consistent throughout the whole language, and derived classes get no special behavior not in accord with any other resolution. This has significant benefits -- for example, in your idea, which version should be called?

    Code:
    struct A{
      virtual void f(int);
    };
    
    struct B:public A{
       virtual void f(double);
    };
    
    B myB;
    myB.f(1); // which f()?
    Under your method of thinking, f(1) is f(int) and thus should call A::f(int) -- which may well NOT be what the user of B expects to happen.

  11. #11
    Registered User Dante Shamest's Avatar
    Join Date
    Apr 2003
    Posts
    970
    This has significant benefits -- for example, in your idea, which version should be called?
    The designers of Java predicted situations like this would cause ambiguity and tried to discourage it altogether.

    Embarrassing situations like this are prevented (in Java) with the compiler error:

    reference to function is ambiguous, both method f(int) in A and method f(double) in B match

  12. #12
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Originally posted by Dante Shamest
    The designers of Java predicted situations like this would cause ambiguity and tried to discourage it altogether.

    Embarrassing situations like this are prevented (in Java) with the compiler error:
    This is a perfect example of when a compiler should complain. The difference is that there is a *real* ambiguity that the compiler simply can't resolve. By the way Cat that was an exellent example.

    Anyway, I can't help it that I hate stupid rules. I'm a Texan.

    Another great annoyance to me, come to think of it, (since I'm already on a tirade) is the keyword restriction imposed upon us. After all, why *shouldn't* you be able to do (unless it's really ambiguous, that is):

    Code:
    struct new{};
    
    new * new  = new new;

    Oh well, I guess I'll have to wait till I'm done with my compiler for that one. :--)
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  13. #13
    ¡Amo fútbol!
    Join Date
    Dec 2001
    Posts
    2,138
    Do you want to code a compiler that can figure that out?

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 3
    Last Post: 10-28-2009, 09:25 AM
  2. What is a virtual function pointer?
    By ting in forum C++ Programming
    Replies: 4
    Last Post: 03-05-2008, 02:36 AM
  3. We Got _DEBUG Errors
    By Tonto in forum Windows Programming
    Replies: 5
    Last Post: 12-22-2006, 05:45 PM
  4. Replies: 5
    Last Post: 02-08-2003, 07:42 PM
  5. Interface Question
    By smog890 in forum C Programming
    Replies: 11
    Last Post: 06-03-2002, 05:06 PM