Fun with member-function pointers

Discussion in 'C++' started by Default User, Aug 22, 2011.

  1. Default User

    Default User Guest

    I've been working on a project that implements instrument drivers. The API
    uses operation codes to direct behavior. All the drivers are subclasses of a
    common ABC. In my implementation, I used a method of mapping the op codes
    to handlers for that operation. That was accomplished with pointers to
    member functions.

    Someone asked me why I didn't have the handler storage and lookup in the
    base class. My initial response was that the pointers had different types.
    Then I got thinking that such a condition doesn't normally slow us down. So
    I created a test case (skipping the map and all).

    This worked, in that it built and seem to run ok. I'm not overly thrilled
    with the cast necessary to store the func pointer. Does anyone see a
    problem? Improvements?


    Brian

    ========= code =========

    #include <iostream>

    #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))

    class tbase
    {
    public:
    tbase() : p(0)
    {
    }
    typedef int (tbase::*HandlerPtr)(int);
    protected:
    HandlerPtr p;
    };
    class test : tbase
    {
    public:
    test()
    {
    p = static_cast<HandlerPtr>(&test::f);
    }
    int f(int i)
    {
    std::cout << "f: " << i << "\n";
    return i;
    }
    void m()
    {
    CALL_HANDLER(*this, p)(1);
    }
    };

    int main()
    {
    test t;
    t.m();
    return 0;
    }
     
    Default User, Aug 22, 2011
    #1
    1. Advertising

  2. On 8/22/2011 5:41 PM, Default User wrote:
    > I've been working on a project that implements instrument drivers. The API
    > uses operation codes to direct behavior. All the drivers are subclasses of a
    > common ABC. In my implementation, I used a method of mapping the op codes
    > to handlers for that operation. That was accomplished with pointers to
    > member functions.
    >
    > Someone asked me why I didn't have the handler storage and lookup in the
    > base class. My initial response was that the pointers had different types.
    > Then I got thinking that such a condition doesn't normally slow us down. So
    > I created a test case (skipping the map and all).
    >
    > This worked, in that it built and seem to run ok. I'm not overly thrilled
    > with the cast necessary to store the func pointer. Does anyone see a
    > problem? Improvements?
    >
    >
    > Brian
    >
    > ========= code =========
    >
    > #include<iostream>
    >
    > #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))


    I'd probably tried to change this to

    CALL_HANDLER(o, p, args) ((o).*(p)) args

    (see below for the use).

    >
    > class tbase
    > {
    > public:
    > tbase() : p(0)


    Perhaps (a) it shouldn't be public (do you intend to instantiate this?
    It's an ABC, isn't it?) and (b) it should have the argument to init the
    'p' with (defautl to 0):

    protected:
    tbase(HandlerPtr pp) : p(pp) {}

    > {
    > }


    Move the typedef above the c-tor:

    > typedef int (tbase::*HandlerPtr)(int);
    > protected:
    > HandlerPtr p;
    > };
    > class test : tbase
    > {
    > public:
    > test()
    > {
    > p = static_cast<HandlerPtr>(&test::f);


    Shouldn't you use the initializer list instead of the assignment? See FAQ.

    > }
    > int f(int i)
    > {
    > std::cout<< "f: "<< i<< "\n";
    > return i;
    > }
    > void m()
    > {
    > CALL_HANDLER(*this, p)(1);


    The use
    CALL_HANLDER((this, p, (1));

    > }
    > };
    >
    > int main()
    > {
    > test t;
    > t.m();
    > return 0;
    > }


    There is no real problem yet. Now imagine that somebody thinks of
    deriving from your 'test' and providing their own handler? How would
    they do it? Or can that never happen?

    Another possible solution is to actually have a pointer to function
    instead of pointer to member, and cast 'this' to the specific class,
    which is usually "safer", I think. Not that I know much about that...

    V
    --
    I do not respond to top-posted replies, please don't ask
     
    Victor Bazarov, Aug 23, 2011
    #2
    1. Advertising

  3. Default User

    Default User Guest

    "Victor Bazarov" <> wrote in message
    news:j2uoi5$jgn$...
    > On 8/22/2011 5:41 PM, Default User wrote:
    > I'd probably tried to change this to
    >
    > CALL_HANDLER(o, p, args) ((o).*(p)) args


    What! You dare question the CLC++ FAQ list where I copied that from? I'll
    look at it.

    > (see below for the use).
    >
    >>
    >> class tbase
    >> {
    >> public:
    >> tbase() : p(0)

    >
    > Perhaps (a) it shouldn't be public (do you intend to instantiate this?
    > It's an ABC, isn't it?) and (b) it should have the argument to init the
    > 'p' with (defautl to 0):


    This was just a quicky demo. So I didn't bother with some of that sort of
    thing. I'll check the "real" code though.

    >> class test : tbase
    >> {
    >> public:
    >> test()
    >> {
    >> p = static_cast<HandlerPtr>(&test::f);

    >
    > Shouldn't you use the initializer list instead of the assignment? See
    > FAQ.


    For this case, sure. But the actual code will be putting them in a std::map
    with the op codes as indexes.

    > There is no real problem yet. Now imagine that somebody thinks of
    > deriving from your 'test' and providing their own handler? How would they
    > do it? Or can that never happen?


    I guess I'm not sure what the specific problem you're concerned about. I
    fear my quicky proof-of-concept demo might have been too sketchy.

    > Another possible solution is to actually have a pointer to function
    > instead of pointer to member, and cast 'this' to the specific class, which
    > is usually "safer", I think. Not that I know much about that...


    I don't think so. One goal is to push a lot of behavior down to the base
    class. I can make the handler map and the handler calling function go down
    there. Then the derived class will populate the map with its specific
    handler for each op code. The upper level modules just call with the op
    codes and data.

    I'll look to see if I can rework my demo tomorrow to make it a bit more like
    the real use case.



    Brian
     
    Default User, Aug 23, 2011
    #3
  4. Default User wrote:
    > ========= code =========
    >
    > #include <iostream>
    >
    > #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))
    >
    > class tbase
    > {
    > public:
    > tbase() : p(0)
    > {
    > }
    > typedef int (tbase::*HandlerPtr)(int);
    > protected:
    > HandlerPtr p;
    > };
    > class test : tbase
    > {
    > public:
    > test()
    > {
    > p = static_cast<HandlerPtr>(&test::f);


    This is undefined behavior. It only happens to work as long as pointers
    to tbase and pointers to test are binary the same.

    > }
    > int f(int i)
    > {
    > std::cout << "f: " << i << "\n";
    > return i;
    > }


    This impplementation of f does not access this and will not fail when
    this is bad.

    > void m()
    > {
    > CALL_HANDLER(*this, p)(1);
    > }
    > };
    >
    > int main()
    > {
    > test t;
    > t.m();
    > return 0;
    > }


    To illustrate the point: change your code to (see below) and it will
    sadly fail.


    I would like to have this pattern supported by the language too, but
    there is an important implementation issue.
    Any function of type HandlerPtr expects an object of type tbase* as this
    argument, while the function test::f expects an object of type test* as
    this argument. Normally when the tbase slice of test is accessed the
    compiler generates the required code to access the slice. But this time
    is the other way around (contravariance). The compiler converts test* to
    tbase* to call the function pointer which claims to need tbase*, but the
    function behind p expects test*. Ouch, who does the downcast?
    Also how should the compiler ensure that the this pointer used to invoke
    a function pointer of type HandlerPtr is of type test*. HandlerPtr tells
    you that it is sufficient that you are of type tbase*. So the downcast
    of this to test* might not be allowed at all.

    Think of two static functions
    void (*funcptr1)(tbase*)
    void (*funcptr2)(test*)
    These pointers are incompatible too for the same reason.

    What you need to do the above conversion is a proxy function that
    converts tbase* back to the test*. And of course, this proxy function is
    only allowed, if p is invoked only with objects of type test.

    Member function pointers are not the solution of your problem. You
    should use an observer pattern instead.

    Member function pointers are the other way around. A pointer of type
    void(test::*)() can be used to call functions of type void(tbase::*)(),
    since every function that expects tbase* can also be invoked with test*.
    The compiler will handle the necessary conversion at each invocation of
    the function pointer. For this reason member function pointers are
    larger than one machine size word in general. The compiler usually adds
    offset information for this. (Things get even more complicated if tbase
    is a virtual base class of test.)


    -------------

    #include <iostream>

    #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))

    class tbase
    {
    protected:
    int x;
    public:
    tbase() : p(0), x(99)
    {
    }
    typedef int (tbase::*HandlerPtr)(int);
    protected:
    HandlerPtr p;
    };
    class test : public tbase
    {
    public:
    test()
    {
    p = static_cast<HandlerPtr>(&test::f);
    }
    virtual ~test()
    { std::cout << "~test\n";
    }
    int f(int i)
    {
    std::cout << "f: " << i << "\n";
    std::cout << "x=" << x << "\n"; // ouch!
    return i;
    }
    void m()
    {
    CALL_HANDLER(*this, p)(1);
    }
    };

    int main()
    {
    test t;
    t.m();
    return 0;
    }
     
    Marcel Müller, Aug 23, 2011
    #4
  5. Default User <> wrote:
    > #include <iostream>
    >
    > #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))
    >
    > class tbase
    > {
    > public:
    > tbase() : p(0)
    > {
    > }
    > typedef int (tbase::*HandlerPtr)(int);
    > protected:
    > HandlerPtr p;
    > };
    > class test : tbase
    > {
    > public:
    > test()
    > {
    > p = static_cast<HandlerPtr>(&test::f);
    > }
    > int f(int i)
    > {
    > std::cout << "f: " << i << "\n";
    > return i;
    > }
    > void m()
    > {
    > CALL_HANDLER(*this, p)(1);
    > }
    > };


    My knowledge of the C++ standard fails me at this point. Is it really
    legal to store a method pointer of the derived class type into a method
    pointer variable of the base class type, and then call it like that?

    (I'm pretty certain that if the base class tried to call the method
    through the pointer, using itself as the object, that would be UB (or
    similar). However, in this case it's the derived class that is doing the
    calling, so I'm not completely sure anymore.)
     
    Juha Nieminen, Aug 23, 2011
    #5
  6. Marcel Müller <> wrote:
    >> p = static_cast<HandlerPtr>(&test::f);

    >
    > This is undefined behavior. It only happens to work as long as pointers
    > to tbase and pointers to test are binary the same.


    Could you elaborate? After all, (&test::f) is a pointer to a function,
    not a pointer to an object. Calling the function through that pointer
    might be UB, but is just storing it too?
     
    Juha Nieminen, Aug 23, 2011
    #6
  7. Default User

    Paul Guest

    On Aug 22, 10:41 pm, "Default User" <> wrote:
    > I've been working on a project that implements instrument drivers. The API
    > uses operation codes to direct behavior. All the drivers are subclasses of a
    > common ABC.  In my implementation, I used a method of mapping the op codes
    > to handlers for that operation. That was accomplished with pointers to
    > member functions.
    >
    > Someone asked me why I didn't have the handler storage and lookup in the
    > base class. My initial response was that the pointers had different types..
    > Then I got thinking that such a condition doesn't normally slow us down. So
    > I created a test case (skipping the map and all).
    >
    > This worked, in that it built and seem to run ok. I'm not overly thrilled
    > with the cast necessary to store the func pointer. Does anyone see a
    > problem? Improvements?
    >
    > Brian
    >
    > ========= code =========
    >
    > #include <iostream>
    >
    > #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))
    >
    > class tbase
    > {
    > public:
    >    tbase() : p(0)
    >    {
    >    }
    >    typedef  int (tbase::*HandlerPtr)(int);
    > protected:
    >    HandlerPtr p;};
    >
    > class test : tbase
    > {
    > public:
    >    test()
    >    {
    >       p  = static_cast<HandlerPtr>(&test::f);
    >    }
    >    int f(int i)
    >    {
    >       std::cout << "f: " << i << "\n";
    >       return i;
    >    }
    >    void m()
    >    {
    >       CALL_HANDLER(*this, p)(1);
    >    }
    >
    > };
    >
    > int main()
    > {
    >    test t;
    >    t.m();
    >    return 0;
    >
    >
    >
    > }- Hide quoted text -
    >
    > - Show quoted text -


    Hi

    Would something like this be any use to you?

    class Base{
    public:
    virtual void foo(int i) =0;
    };

    class Derived:public Base{
    public:
    typedef void (Derived::*funcp)(int);
    Derived(){
    arr[0]= &Derived::fun1;
    arr[1]= &Derived::fun2;
    arr[2]= &Derived::fun3;
    }
    void foo(int i){
    (this->*arr)(999);
    }
    void fun1(int i){std::cout<<"In fun1.."<<i<<"..\n";}
    void fun2(int i){std::cout<<"In fun2.."<<i<<"..\n";}
    void fun3(int i){std::cout<<"In fun3.."<<i<<"..\n";}
    private:
    funcp arr[3];
    };

    int main() {
    Base* p = new Derived;
    p->foo(1);
    }


    Im not sure on the details of your program so its hard to understand
    you API requirments and stuff.
    HTH
     
    Paul, Aug 23, 2011
    #7
  8. On 8/23/2011 3:09 AM, Juha Nieminen wrote:
    > Default User<> wrote:
    >> #include<iostream>
    >>
    >> #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))
    >>
    >> class tbase
    >> {
    >> public:
    >> tbase() : p(0)
    >> {
    >> }
    >> typedef int (tbase::*HandlerPtr)(int);
    >> protected:
    >> HandlerPtr p;
    >> };
    >> class test : tbase
    >> {
    >> public:
    >> test()
    >> {
    >> p = static_cast<HandlerPtr>(&test::f);
    >> }
    >> int f(int i)
    >> {
    >> std::cout<< "f: "<< i<< "\n";
    >> return i;
    >> }
    >> void m()
    >> {
    >> CALL_HANDLER(*this, p)(1);
    >> }
    >> };

    >
    > My knowledge of the C++ standard fails me at this point. Is it really
    > legal to store a method pointer of the derived class type into a method
    > pointer variable of the base class type, and then call it like that?
    >
    > (I'm pretty certain that if the base class tried to call the method
    > through the pointer, using itself as the object, that would be UB (or
    > similar). However, in this case it's the derived class that is doing the
    > calling, so I'm not completely sure anymore.)


    Analysing... Analysing...

    My guess is that this "round-trip" (store in the base, but call in the
    derived where the member function actually exists) might be actually a
    bad idea, and here is why. The 'static_cast' used to convert to 'p'
    *can* change the pointer itself (adjust or whatever). Calling the
    member from inside the derived class does not change the fact that the
    type of the pointer is "member of 'tbase'". Only an implicit cast to
    "member of 'test'" could bring it back, so to speak. IOW, the 'm'
    function should contain the declaration and use of test::*, converted
    from 'p', something like:

    void m()
    {
    void (test::*pp)(int) = p;
    CALL_HANDLER(*this, pp)(1);
    }

    Then the conversions (to pointer to member of base, to pointer to member
    of derived) are mutually cancelling.

    V
    --
    I do not respond to top-posted replies, please don't ask
     
    Victor Bazarov, Aug 23, 2011
    #8
  9. Juha Nieminen wrote:
    > Marcel Müller <> wrote:
    >>> p = static_cast<HandlerPtr>(&test::f);

    >> This is undefined behavior. It only happens to work as long as pointers
    >> to tbase and pointers to test are binary the same.

    >
    > Could you elaborate? After all, (&test::f) is a pointer to a function,
    > not a pointer to an object. Calling the function through that pointer
    > might be UB, but is just storing it too?


    Well, the language does no longer define the semantic of the pointer
    stored by this expression. What else would you call undefined behavior?

    Of course, it is not likely to cause harm unless you dereference it. But
    AFAIK the round trip - casting to an incompatible pointer and back to
    the original one - is not guaranteed to work. The only exception is
    casting any object pointer to void* and back to whatever it has been before.


    Marcel
     
    Marcel Müller, Aug 23, 2011
    #9
  10. Victor Bazarov <> wrote:
    > void (test::*pp)(int) = p;


    Can you do that? I haven't tried, but it looks to me like there's an
    attempt to implicitly convert a method pointer of the base class type to
    a method pointer of the derived class type. Is that allowed? (After all,
    if you try to implicitly convert a base class pointer to a derived class
    pointer, you'll get an error.)

    The only way I know of for safely storing derived-type method pointers
    in the base class is for the base class to implement a template wrapper
    type which encloses the derived-type pointer (and which the derived class
    instantiates). The base class can call this without itself having to be
    a template class, if this wrapper itself is derived from a non-templated
    base wrapper, and the derived type instantiation is done dynamically and
    managed by the base class. A virtual function in this base wrapper can
    then be used to call the method from the base class.

    Yeah, it's complicated.
     
    Juha Nieminen, Aug 23, 2011
    #10
  11. Default User

    Paul Guest

    On Aug 23, 11:49 am, Paul <> wrote:
    > On Aug 22, 10:41 pm, "Default User" <> wrote:
    >
    >
    >
    >
    >
    > > I've been working on a project that implements instrument drivers. The API
    > > uses operation codes to direct behavior. All the drivers are subclassesof a
    > > common ABC.  In my implementation, I used a method of mapping the op codes
    > > to handlers for that operation. That was accomplished with pointers to
    > > member functions.

    >
    > > Someone asked me why I didn't have the handler storage and lookup in the
    > > base class. My initial response was that the pointers had different types.
    > > Then I got thinking that such a condition doesn't normally slow us down.. So
    > > I created a test case (skipping the map and all).

    >
    > > This worked, in that it built and seem to run ok. I'm not overly thrilled
    > > with the cast necessary to store the func pointer. Does anyone see a
    > > problem? Improvements?

    >
    > > Brian

    >
    > > ========= code =========

    >
    > > #include <iostream>

    >
    > > #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))

    >
    > > class tbase
    > > {
    > > public:
    > >    tbase() : p(0)
    > >    {
    > >    }
    > >    typedef  int (tbase::*HandlerPtr)(int);
    > > protected:
    > >    HandlerPtr p;};

    >
    > > class test : tbase
    > > {
    > > public:
    > >    test()
    > >    {
    > >       p  = static_cast<HandlerPtr>(&test::f);
    > >    }
    > >    int f(int i)
    > >    {
    > >       std::cout << "f: " << i << "\n";
    > >       return i;
    > >    }
    > >    void m()
    > >    {
    > >       CALL_HANDLER(*this, p)(1);
    > >    }

    >
    > > };

    >
    > > int main()
    > > {
    > >    test t;
    > >    t.m();
    > >    return 0;

    >
    > > }- Hide quoted text -

    >
    > > - Show quoted text -

    >
    > Hi
    >
    > Would something like this be any use to you?
    >
    > class Base{
    > public:
    >         virtual void foo(int i) =0;
    >
    > };
    >
    > class Derived:public Base{
    > public:
    >         typedef void (Derived::*funcp)(int);
    >         Derived(){
    >                 arr[0]= &Derived::fun1;
    >                 arr[1]= &Derived::fun2;
    >                 arr[2]= &Derived::fun3;
    >         }
    >         void foo(int i){
    >                 (this->*arr)(999);
    >         }
    >         void fun1(int i){std::cout<<"In fun1.."<<i<<"..\n";}
    >         void fun2(int i){std::cout<<"In fun2.."<<i<<"..\n";}
    >         void fun3(int i){std::cout<<"In fun3.."<<i<<"..\n";}
    > private:
    >         funcp arr[3];
    >
    > };
    >
    > int main() {
    >         Base* p = new Derived;
    >         p->foo(1);
    >
    > }
    >
    > Im not sure on the details of your program so its hard to understand
    > you API requirments and stuff.
    > HTH- Hide quoted text -
    >
    > - Show quoted text -


    This could be implemented with the function pointers in the Base class
    by moving the array to the Base class like so:

    class Derived;
    class Base{
    public:
    typedef void (Derived::*funcp)(int);
    virtual void foo(int i) =0;
    protected:
    funcp arr[3];
    };

    class Derived:public Base{...


    But I don't see what difference it makes if they're stored in base or
    the derived class because you can't use the function pointers without
    an instance of a derived class that populates the array(or map if your
    using a map).

    I think this seems a bit tidier than casting the function pointers.
     
    Paul, Aug 23, 2011
    #11
  12. Default User

    Default User Guest

    "Marcel Müller" <> wrote in message
    news:4e5351c3$0$6566$-online.net...

    > This is undefined behavior.


    I think that I'm convinced that you're correct. The example I had built
    without issue and gave me the results I wanted, but as we know UB can do
    that. Right up until someone changes something and then it doesn't.

    > To illustrate the point: change your code to (see below) and it will sadly
    > fail.


    You are correct, although adding a virtual destructor to the base class
    makes it "work" again.

    > Member function pointers are not the solution of your problem. You should
    > use an observer pattern instead.


    I'm not sure how that pattern would apply to this situation. Perhaps I'll
    start a new topic on the more general question.

    If nothing else, what I have now works and is safe. It just leads to
    repetitive code in all the modules (eventually ten or more). I had hoped to
    push this down to the base class.



    Brian
     
    Default User, Aug 23, 2011
    #12
  13. On 8/23/2011 9:34 AM, Juha Nieminen wrote:
    > Victor Bazarov<> wrote:
    >> void (test::*pp)(int) = p;

    >
    > Can you do that? I haven't tried, but it looks to me like there's an
    > attempt to implicitly convert a method pointer of the base class type to
    > a method pointer of the derived class type. Is that allowed? (After all,
    > if you try to implicitly convert a base class pointer to a derived class
    > pointer, you'll get an error.)


    Yes, it's allowed. And implicit conversion for pointers to members
    works exactly the opposite way to pointers to objects. Objects: if it's
    a pointer to derived, it can be converted to pointer to base, members:
    if it's a member of base, it can be implicitly converted to member of
    the derived. Reverse conversions require a static cast, which should
    succeed (and well-defined) if the original implicit conversion exists.


    It's actually logical, if you think about it. A derived class does
    contain the base class subobject, a pointer to which can easily be
    figured out (calculated) by the compiler. And if there is a member in
    the base class, a way to access it through the derived object can also
    be easily figured out (by adding a conversion from 'derived' to 'base').

    As to the validity of the round-trip, I am not so sure.

    > The only way I know of for safely storing derived-type method pointers
    > in the base class is for the base class to implement a template wrapper
    > type which encloses the derived-type pointer (and which the derived class
    > instantiates). The base class can call this without itself having to be
    > a template class, if this wrapper itself is derived from a non-templated
    > base wrapper, and the derived type instantiation is done dynamically and
    > managed by the base class. A virtual function in this base wrapper can
    > then be used to call the method from the base class.
    >
    > Yeah, it's complicated.


    No joke...

    V
    --
    I do not respond to top-posted replies, please don't ask
     
    Victor Bazarov, Aug 23, 2011
    #13
  14. On 8/22/2011 2:41 PM, Default User wrote:
    >
    > This worked, in that it built and seem to run ok. I'm not overly thrilled
    > with the cast necessary to store the func pointer. Does anyone see a
    > problem? Improvements?
    >


    In C++ language member function pointers are _contravariant_. This means
    that you these pointers are implicitly convertible down the class
    hierarchy. Pointer conversions up the class hierarchy are also allowed
    with explicit use of `static_cast`, which is exactly what you did in
    your code.

    Compare this to ordinary object pointers, which are _covariant_. With
    object pointers it is the opposite: conversions up the hierarchy are
    implicit, while conversions down the hierarchy require an explicit cast
    (`static_cast` or `dynamic_cast`).

    In other words, the "trick" you use in your code is perfectly legal. Its
    functionality is guaranteed by the language specification.

    --
    Best regards,
    Andrey Tarasevich
     
    Andrey Tarasevich, Aug 24, 2011
    #14
  15. On 8/23/2011 12:07 AM, Marcel Müller wrote:
    >>
    >> class tbase
    >> {
    >> public:
    >> tbase() : p(0)
    >> {
    >> }
    >> typedef int (tbase::*HandlerPtr)(int);
    >> protected:
    >> HandlerPtr p;
    >> };
    >> class test : tbase
    >> {
    >> public:
    >> test()
    >> {
    >> p = static_cast<HandlerPtr>(&test::f);

    >
    > This is undefined behavior. It only happens to work as long as pointers
    > to tbase and pointers to test are binary the same.


    Absolutely incorrect. This cast is perfectly legal. This use of
    `static_cast` is explicitly allowed and supported by the language
    specification.

    Actually, if this cast suffered from the problem you imply, it would
    simply be disallowed. C++ style casts were specifically introduced to
    outlaw straightforward hacks.

    > To illustrate the point: change your code to (see below) and it will
    > sadly fail.


    No, the code you provided below also works perfectly fine, as it should.
    The only reason it might "fail" is the non-compliant compiler that
    attempts to save memory by using different pointer representation for
    different hierarchy types. Such compiler usually have an option to
    restore the proper behavior.

    > I would like to have this pattern supported by the language too, but
    > there is an important implementation issue.


    This "pattern" has been supported by the language since the very first
    standard of C++.

    > Any function of type HandlerPtr expects an object of type tbase* as this
    > argument, while the function test::f expects an object of type test* as
    > this argument. Normally when the tbase slice of test is accessed the
    > compiler generates the required code to access the slice. But this time
    > is the other way around (contravariance). The compiler converts test* to
    > tbase* to call the function pointer which claims to need tbase*, but the
    > function behind p expects test*. Ouch, who does the downcast?


    The compiler does it. It is required to perform the downcast by the
    language standard. This downcast is in general case a non-trivial
    operation, which is why a correctly implemented pointer to member
    function has to carry some extra information with it (and which is why
    its size is generally larger than that of an ordinary pointer). This
    extra information is there specifically to allow the compiler to
    generate the code for proper run-time downcast.

    > Also how should the compiler ensure that the this pointer used to invoke
    > a function pointer of type HandlerPtr is of type test*.


    It doesn't. It is your responsibility. If you attempt to access a member
    that doesn't really exist, the behavior is undefined.

    > Member function pointers are not the solution of your problem. You
    > should use an observer pattern instead.


    They are. They are not developed very well, meaning that some pattern
    (like Observer) might indeed be a better solution. But your specific
    arguments against the pointer are based on totally incorrect premise.

    > Member function pointers are the other way around. A pointer of type
    > void(test::*)() can be used to call functions of type void(tbase::*)(),
    > since every function that expects tbase* can also be invoked with test*.
    > The compiler will handle the necessary conversion at each invocation of
    > the function pointer. For this reason member function pointers are
    > larger than one machine size word in general. The compiler usually adds
    > offset information for this. (Things get even more complicated if tbase
    > is a virtual base class of test.)


    You are almost right. Member function pointers are indeed "the other way
    around". Member function pointers are contravariant. They support
    implicit conversion down the hierarchy, as well as they support an
    explicit conversion up the hierarchy. The latter is explicitly allowed
    by the specification of `static_cast`.

    --
    Best regards,
    Andrey Tarasevich
     
    Andrey Tarasevich, Aug 24, 2011
    #15
  16. On 8/23/2011 8:36 AM, Default User wrote:
    >
    >> This is undefined behavior.

    >
    > I think that I'm convinced that you're correct. The example I had built
    > without issue and gave me the results I wanted, but as we know UB can do
    > that. Right up until someone changes something and then it doesn't.


    No. That poster is flat out incorrect.

    >> To illustrate the point: change your code to (see below) and it will sadly
    >> fail.

    >
    > You are correct, although adding a virtual destructor to the base class
    > makes it "work" again.


    Again, incorrect. The code that supposedly "fails" doesn't fail at all
    :) As I said already, a spurious failure can be caused by incorrect
    compiler setup. Some compilers (like MSVC++) behave non-compliantly in
    this regard by default. However, if you use proper compiler setting
    (i.e. if you use a standard compliant compiler) the code will work.

    --
    Best regards,
    Andrey Tarasevich
     
    Andrey Tarasevich, Aug 24, 2011
    #16
  17. On 8/23/2011 12:09 AM, Juha Nieminen wrote:
    >
    > My knowledge of the C++ standard fails me at this point. Is it really
    > legal to store a method pointer of the derived class type into a method
    > pointer variable of the base class type, and then call it like that?


    Yes. It is absolutely legal. This is described explicitly in the
    specification of `static_cast`. This is one of the things `static_cast`
    is for.

    > (I'm pretty certain that if the base class tried to call the method
    > through the pointer, using itself as the object, that would be UB (or
    > similar).


    Exactly. This is described explicitly in the specification of `->*`/`.*`
    operators.

    > However, in this case it's the derived class that is doing the
    > calling, so I'm not completely sure anymore.)


    It doesn't really matter who is doing the calling.

    This part of `static_cast` specification was added to C++ between the
    last draft of C++98 and the final version of C++98. So, in the last
    draft it is still illegal, but the actual standard itself explicitly
    supports it. For this reason, one can come across pre-standard Usenet
    post from some quite respected individuals claiming that this is illegal
    (which often fuels the confusion). In reality, this is allowed in
    standard C++.

    --
    Best regards,
    Andrey Tarasevich
     
    Andrey Tarasevich, Aug 24, 2011
    #17
  18. On 8/23/2011 5:04 AM, Victor Bazarov wrote:
    >...
    > Then the conversions (to pointer to member of base, to pointer to member
    > of derived) are mutually cancelling.
    > ...


    That's great, but the fact is that C++ language allows absolutely
    unrestricted `static_cast`-ing if member function pointers up the
    hierarchy. Also, at the point where the pointer gets dereferenced (i.e.
    at the point of the call) the only requirement that C++ language imposes
    is that the _dynamic_ type of the object used on the left-hand side of
    `->*`/`.*` operators shall contain the member the pointer is pointing
    to. There are no other restrictions whatsoever.

    In order to implement this specification one has to know how to do the
    proper adjustment of `this` value at the point of the call. How is it
    done? It is done by compiler magic.

    In the OPs case it is easy, since no adjustment is needed.

    In the allegedly "non-working" example posted by Marcel Müller it is
    also pretty very easy. In a typical implementation the pointer object
    itself stores an extra "`this` adjustment offset" value, which is used
    at run-time to obtain the proper `this` value for the call. This is also
    sufficient for multiple inheritance situations.

    Things usually become difficult and heavy in virtual inheritance
    situations. However, this is still doable and every compliant compiler
    does it.

    --
    Best regards,
    Andrey Tarasevich

    P.S. It actually appears that MSVC++ compiler is broken to the point
    where it can't handle Marcel Müller's example regardless of the
    settings. GCC on the other hand has no issues with it whatsoever.
     
    Andrey Tarasevich, Aug 24, 2011
    #18
  19. Default User wrote:
    > "Marcel Müller" <> wrote in message
    > news:4e5351c3$0$6566$-online.net...
    >
    >> This is undefined behavior.

    >
    > I think that I'm convinced that you're correct. The example I had built
    > without issue and gave me the results I wanted, but as we know UB can do
    > that. Right up until someone changes something and then it doesn't.
    >
    >> To illustrate the point: change your code to (see below) and it will sadly
    >> fail.

    >
    > You are correct, although adding a virtual destructor to the base class
    > makes it "work" again.


    Use multiple inheritance and it will fail again.


    >> Member function pointers are not the solution of your problem. You should
    >> use an observer pattern instead.

    >
    > I'm not sure how that pattern would apply to this situation. Perhaps I'll
    > start a new topic on the more general question.


    The base class should provide an event (or something like that) where
    derived classes could register a handler. The handler should handle the
    operations. It's your choice whether the dispatch table is in the base
    (i.e. one handler for each command) or if the dispatch is in the derived
    class (i.e. each handler catches the operations he can handle and the
    framework tries all handlers until one does the work or it finally fails
    - like a window message procedure).


    Marcel
     
    Marcel Müller, Aug 24, 2011
    #19
  20. Marcel Müller wrote:
    >>
    >>> To illustrate the point: change your code to (see below) and it will
    >>> sadly fail.

    >>
    >> You are correct, although adding a virtual destructor to the base
    >> class makes it "work" again.

    >
    > Use multiple inheritance and it will fail again.


    No. Use a compliant C++ compiler, and it will not fail, regardless of
    the type of inheritance and/or polymorphism :)

    GCC, for one example, will handle all these cases correctly.

    MSVC++, incidentally, has compiler settings that make it handle multiple
    (and virtual) inheritance correctly, but for some bizarre reason it
    fails on your the original (simple) example regardless of the settings.

    --
    Best regards,
    Andrey Tarasevich
     
    Andrey Tarasevich, Aug 24, 2011
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Andy Fish
    Replies:
    65
    Views:
    1,765
    Mabden
    May 18, 2004
  2. n2xssvv g02gfr12930

    Smart pointers and member function pointers

    n2xssvv g02gfr12930, Nov 26, 2005, in forum: C++
    Replies:
    3
    Views:
    472
    n2xssvv g02gfr12930
    Nov 27, 2005
  3. dolphin
    Replies:
    4
    Views:
    323
    Jorgen Grahn
    Aug 25, 2007
  4. Hamish
    Replies:
    3
    Views:
    580
    Alf P. Steinbach
    Jan 25, 2008
  5. er
    Replies:
    2
    Views:
    509
Loading...

Share This Page