There is a forward C struct declared in an unmodifiable header. I would like to "virtually" add convenience member functions to it. Obviously my first choice would be to extend the struct and add the methods to the derived class. No-can-do, as the struct itself is declared as "forward" in the header, so I get the error "error: invalid use of incomplete type ...". I get a similar error if I try to define a new struct with a single element of the old struct. This sucks.
However, I was thinking that I could do some hackery with reinterpret_cast to get it to work anyway. The way it would go is this:
//defined in header
struct A forward;
void do_something_with_A(A* a, int arg);
//defined in my wrapper
struct B {
B* wrap(A* a) {return reinterpret_cast<B*>(a); }
void do_something(int arg) {do_something_with_A(reinterpret_cast<A*>(this),arg); }
}
If I added implicit conversions from type B to type A, I was thinking that this could work out almost as if B was a zero-data inheritor of A. However, this obviously brings up the question: is this undefined in C++? Normally I would think that accessing an element of an illegally casted struct would be undefined; that makes sense. However, I would think that reinterpret_casting from one type to another, passing that pointer around, and then casting back again, without doing anything illegal in between would be fine. I would also think that the way a compiler implements non-virtual struct members would be creating a function
B::do_something(B* b, int arg)
and calling that with the appropriate argument for B. This then reduces to the previous case, which by my dubious logic is okay. So I would think calling .do_something on a struct which is actually a reinterpret_cast A would be okay.
However, this says nothing for what the C++ standard actually says on the matter. Any help with that? Also, if someone has information on how well this will work practically, (i.e. "Every compiler ever made accepts this", or "this only works with a few compilers") that would also be helpful, but slightly less so.
I don't believe this works if you're using static_cast
because you cannot static_cast
between two completely unrelated class types. To be specific, if you have a pointer of type A*
and try to convert it to a pointer of type B*
, the static_cast
only succeeds if this declaration is valid:
B* ptr(myAPtr);
or if B
is non-virtually derived from A
(which it isn't). See the ISO spec §5.2.9 for details on the specifics of this. If we consider the above declaration, the only possible conversions that could be applied here in all of §4 are those in §4.10, and of those the only one that might be applicable is conversion from base to derived classes (§4.10/3), but this doesn't apply here because A
and B
aren't related types.
The only cast you might be able to use here is a reinterpret_cast
, and it doesn't look like this will work either. In particular, the behavior of casting across class hierarchies is (§5.2.10/7)
A pointer to an object can be explicitly converted to a pointer to an object of different type.65) Except that converting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.
So immediately there's no guarantee that anything is going to work if the two objects have different alignment restrictions, and you cannot ensure that this is true. But suppose that you could. In that case, though, I believe that this will actually work correctly! Here's the reasoning. When you call the member function of the B
object, then rule &5.2.2/1) kicks in and says that, since the function is nonvirtual:
[...] The function called in a member function call is normally selected according to the static type of the object expression. [...]
Okay, so we're at least calling the right function. Now, what about the this
pointer? Well, according to &5.2.2/4:
[...] If the function is a nonstatic member function, the “this” parameter of the function (9.3.2) shall be initialized with a pointer to the object of the call, converted as if by an explicit type conversion (5.4). [...]
The type conversion done in the last part is the identity conversion from a B*
to a B*
, since that's the selected type. So you've called the right function with the this
pointer set appropriately. Nice! Finally, when you do a reinterpret_cast
back to the original type, by the earlier rule, you'll get back the A*
object and everything will go as expected.
Of course, this only works if the objects have the same alignment requirements, and this cannot be guaranteed. Consequently, you shouldn't do it!
Hope this helps!