tags:

views:

141

answers:

5

Suppose I have two classes with identical members from two different libraries:

namespace A {
  struct Point3D {
    float x,y,z;
  };
}

namespace B {
  struct Point3D {
    float x,y,z;
  };
}

When I try cross-casting, it worked:

A::Point3D pa = {3,4,5};
B::Point3D* pb = (B::Point3D*)&pa;
cout << pb->x << " " << pb->y << " " << pb->z << endl;

Under which circumstances is this guaranteed to work? Always? Please note that it would be highly undesirable to edit an external library to add an alignment pragma or something like that. I'm using g++ 4.3.2 on Ubuntu 8.10.

+1  A: 

If the structs you are using are just data and no inheritance is used I think it should always work.

As long as they are POD it should be ok. http://en.wikipedia.org/wiki/Plain_old_data_structures

According to the standard(1.8.5)

"Unless it is a bit-field (9.6), a most derived object shall have a non-zero size and shall occupy one or more bytes of storage. Base class subobjects may have zero size. An object of POD5) type (3.9) shall occupy contiguous bytes of storage."

If they occupy contiguous bytes of storage and they are the same struct with different name, a cast should succeed

Arkaitz Jimenez
As I read that quote from the standard, it doesn't rule out padding, or guarantee that the two structs will use identical padding. I agree, it'll almost certainly work in practice though.
jalf
If both structs are defined the same and the compiler is the same the padding should be identical.
Arkaitz Jimenez
A: 

I know exactly it wouldn't work:

  • both struct has differen alignment;
  • compiled with different RTTI options

may be some else...

Dewfy
From http://en.wikipedia.org/wiki/RTTI:"RTTI is only available for classes which are polymorphic, which means they have at least one virtual method." Does your concern still apply to POD?Under what conditions would they have different alignment?
Jann
It's not so. There is C++ keyword typeid - that should return identifier for any type (regardless of usage), so compiler need to embed small chunk to any struct. Even more, we don't know how this structure will be used by end-programmer. So for single instance this conversion will be correct, but for array may fail.
Dewfy
the compiler does *not* embed anything in any struct. It's easy to verify this with sizeof(). You can easily create a struct of size 1. If you create an empty struct, it can even have size 0 if it is used as a base class.
jalf
It is large difference sizeof created to return correct value (sizeof(float) * 3 - for example above) But A::Point3D inst1[3] - can be non equal to sizeof( A::Point3D ) * 3.The reason I have posted in answer (1) - alignment (2) rtti info
Dewfy
A: 

This line should be :

B::Point3D* pb = (B::Point3D*)&pa;

Note the &. I think what you are doing is a reinterpret_cast between two pointers. In fact you can reinterpret_cast any pointer type to another one, regardless of the type of the two pointers. But this unsafe, and not portable.

For example,

int x = 5;
double* y = reinterpret_cast<double*>(&x);

You are just going with the C-Style, So the second line is actually equal to:

double* z = (double*)&x;

I just hate the C-Style when casting because you can't tell the purpose of the cast from one look :)


Under which circumstances is this guaranteed to work?

This is not real casting between types. For example,

int i = 5;
float* f = reinterpret_cast<float*>(&i);

Now f points to the same place that i points to. So, no conversion is done. When you dereference f, you will get the a float with the same binary representation of the integer i. It is four bytes on my machine.

AraK
reinterpret_cast would be a bad idea, as it's technically not guaranteed to result in a pointer to the same address. static_cast<double*>(static_cast<void*>()) guarantees a pointer to the same address.
jalf
Thanks for the correction.
Jann
+1  A: 

If two POD structs start with the same sequence of members, the standard guarantees that you'll be able to access them freely through a union. You can store an A::Point3D in a union, and then read from the B::Point3D member, as long as you're only touching the members that are part of the initial common sequence. (so if one struct contained int, int, int, float, and the other contained int, int, int, int, you'd only be allowed to access the three first ints).

So that seems like one guaranteed way in which your code should work. It also means the cast should work, but I'm not sure if this is stated explicitly in the standard.

Of course all this assumes that both structs are compiled with the same compiler to ensure identical ABI.

jalf
+1: Excellent point. It would be a very strange compiler that decided to treat POD-struct's differently when nested in a union or not.
Richard Corden
A: 

The following is pretty safe:

namespace A {
  struct Point3D {
    float x,y,z;
  };
}

namespace B {
    typedef A::Point3D Point3D;
}

int main() {
    A::Point3D a;
    B::Point3D* b = &a;

    return 0;
}
Kirill V. Lyadvinsky