Fun with member-function pointers

D

Default User

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;
}
 
V

Victor Bazarov

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
 
D

Default User

Victor Bazarov said:
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).


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

Marcel Müller

Default said:
========= 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;
}
 
J

Juha Nieminen

Default User said:
#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.)
 
J

Juha Nieminen

Marcel Müller said:
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?
 
P

Paul

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
 
V

Victor Bazarov

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
 
M

Marcel Müller

Juha said:
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
 
J

Juha Nieminen

Victor Bazarov said:
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.
 
P

Paul

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?

========= 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.
 
D

Default User

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
 
V

Victor Bazarov

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
 
A

Andrey Tarasevich

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

Andrey Tarasevich

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`.
 
A

Andrey Tarasevich

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

Andrey Tarasevich

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++.
 
A

Andrey Tarasevich

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

Marcel Müller

Default said:
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.


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

Use multiple inheritance and it will fail again.

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
 
A

Andrey Tarasevich

Marcel said:
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.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top