4

I have a simple point struct that I would like to inherit:

struct PointXYZ
{
    double x, y, z;
};

class extendedPoint : public PointXYZ
{
public:
    double getXYZ(int i);
    void setXYZ(int i, double value);
private:
    int moreproperties;
};

The get-set function is indexed because I'd like to be able to loop through getting and setting the xyz values. I'd like to link the concept of indexed xyz and standalone x,y,z by modifying my base class as described in the answer to this post:

struct PointXYZ
{
    union {
        struct {
            double x, y, z;
        };
        double xyz[3];
    };
};

class extendedPoint : public PointXYZ
{
public:
    double getXYZ(int i) { return xyz[i]; }
    void setXYZ(int i, double value) { xyz[i] = value; }
private:
    int moreproperties;
};

But this post directly contradicts the first post on whether the following is valid:

double dostuff()
{
    PointXYZ p;
    p.x = 123.88;
    return p.xyz[0];
}

So, is PointXYZ's use of union valid c++ and consistent between compilers or not?

Community
  • 1
  • 1
Phlucious
  • 3,704
  • 28
  • 61

2 Answers2

7

From the standard:

In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time. [ Note: One special guarantee is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout structs that share a common initial sequence (9.2), and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members; see 9.2. — end note ]

Seems to me that the special guarantee indicates that

struct PointXYZ {
    union {
        struct {
            double xyz[3];
        };
        struct {
            double x, y, z;
        };
    };
};

should work just fine when reading the member that was not last written (See it happen).

Note, however, that according to the specs, your example of

struct PointXYZ {
    union {
        struct {
            double xyz[3];
        };
        double x, y, z;
    };
};

is undefined behaviour when reading the member that was not last written (See it happen). When setting xyz[0], xyz[1] and xyz[2], all of x, y and z have the same value as xyz[0] in the linked example. I would expect most compilers to behave like this, but the standard does not guarantee it.

Bart van Nierop
  • 4,130
  • 2
  • 28
  • 32
  • I don't remember really, but how about packing/alignment? Does arrays have same rules as struct fields? Shouldn't be packing/alignment explicitely set here? – quetzalcoatl Mar 01 '14 at 09:17
  • @quetzalcoatl As far as I'm aware, both should occupy contiguous memory. I have to admit I'm not 100% on that, though. – Bart van Nierop Mar 01 '14 at 09:55
  • 1
    Do the two anonymous structs share a common initial sequence? – manlio Mar 02 '14 at 10:12
  • That's an interesting exception you found in the std. Which compiler did you use for those examples? – Phlucious Mar 03 '14 at 16:59
  • Thanks to your examples I just noticed that my original example code was wrong in a very obvious way. I have switched which member is wrapped in a struct to avoid the obvious error. – Phlucious Mar 03 '14 at 17:42
1
struct PointXYZ {
  union {
    double xyz[3];
    struct {
      double x, y, z;
    };
  };
};

Depending on the compiler / platform, you may have data alignment issues (it's possible though unlikely).

x, y, z may not reside in memory in correspondence with xyz[0], xyz[1], xyz[2].

C++ guarantees that elements of the xyz array are laid out contiguously, but it only guarantees that the address of an element of the struct is greater than the address of all earlier-declared elements (of course many compilers offer #pragma to control packing and alignment).

About the type punning matter (the practice of reading from a different union member than the one most recently written to)... you could say that this isn't type punning (types match, so there is no pun, it's merely aliasing), but still, from an the viewpoint of the language standard, the member written to and read from are different, which is undefined in C++.

Anyway, generally, you'll have the expected result...

With GCC it's even guaranteed to work (even with -fstrict-aliasing):

As a side note consider that anonymous unions are permitted but C++ prohibits anonymous structs (a lot of compilers allow them).

What about the following solution?

class PointXYZ
{
public:
  double getXYZ(unsigned i) { return xyz[i]; }
  void setXYZ(unsigned i, double value) { xyz[i] = value; }

  double &x() { return xyz[0]; }
  double &y() { return xyz[1]; }
  double &z() { return xyz[2]; }

private:
  double xyz[3];
};
manlio
  • 18,345
  • 14
  • 76
  • 126
  • This seems like a viable solution, but I was hoping to keep the PointXYZ interface as simple as possible (fewer keystrokes). – Phlucious Mar 03 '14 at 17:46
  • Ah yes... I didn't do this option because MSVC2010 doesn't support initializer lists, so I wanted the private members `x, y, z` instead of `xyz[3]`, which makes the `getXYZ()` and `setXYZ()` implementations a little weird. The easiest implementation I could think of was `double getXYZ(int i) { return (&x)[i]; }` but that seems to introduce other potential issues... – Phlucious Mar 03 '14 at 18:20