13

There's no way to do something like this, in C++ is there?

union {
    {
        Scalar x, y;
    }
    Scalar v[2];
};

Where x == v[0] and y == v[1]?

timrau
  • 22,578
  • 4
  • 51
  • 64
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 3
    Please be aware that while C++ guarantees that elements of an array are laid out contiguously, it only guarantees that the address of an element of a POD struct is greater than the address of all earlier-declared elements. This means it's possible (though unlikely) that v[1] and y don't correspond. – j_random_hacker Apr 01 '09 at 02:12
  • @ j_random_hacker: Are you sure? I believe there's another rule, inherited from C, from which yoiu can derive that there's no initial padding (something in the aliasing rules IIRC, about accessing a struct via a pointer of the type of the first element) – MSalters Sep 27 '10 at 08:34
  • 2
    Yeah, that rule exists in C++ as well. There's no initial padding, but I think @j_random_hackers point is that there might be padding in the struct after the first element (and before the second one) – jalf Sep 27 '10 at 08:46

8 Answers8

17

Since you are using C++ and not C, and since they are of the same types, why not just make x a reference to v[0] and y a reference to v[1]

Brian R. Bondy
  • 339,232
  • 124
  • 596
  • 636
  • 2
    +1. This nicely gets around the fact that v[1] is not guaranteed by C++ language rules to correspond to y. – j_random_hacker Apr 01 '09 at 02:10
  • Assuming you mean for the code operating on the struct to declare these references as local variables: the compiler will optimise them away, so there's no need to t worry about performance. – j_random_hacker Apr 01 '09 at 02:11
16

How about

union {
    struct {
        int x;
        int y;
    };
    int v[2];
};

edit:

union a {
    struct b { int first, second; } bee;
    int v[2];
};

Ugly, but that's more accurate

fido
  • 5,566
  • 5
  • 24
  • 20
  • 1
    Wouldn't you give the struct a name so you can reference union_name.struct_name.x? – Tom Ritter Mar 31 '09 at 19:31
  • I'd collapse your "b bee" line such that you end up with "struct b { ... } bee;" – rmeador Mar 31 '09 at 19:42
  • You don't need to name the union ('b'), only the member ('bee'). – David Rodríguez - dribeas Mar 31 '09 at 20:23
  • 3
    Depending on what type x, y, and z are, and which specific compiler you're using, and even what the target platform is, you may see issues caused by data alignment. Essentially, x and y may not necessarily reside in memory in direct correspondence with v[0] and v[1]. – John Watts Mar 31 '09 at 21:01
  • John, to fix that could you pack the structure (again compiler and platform dependent)? – fido Mar 31 '09 at 21:41
  • I don't see the point of naming the struct... that just adds an extra unwanted layer of encapsulation. – mpen Mar 31 '09 at 23:06
  • 2
    +1 John Watts. C++ guarantees that elements of an array are laid out contiguously, but only guarantees that the address of an element of a POD struct is greater than the address of all earlier-declared elements (i.e. there is more flexibility in laying out structs). – j_random_hacker Apr 01 '09 at 02:06
  • 4
    At least, please add a compile-time assertion that offsetof(v[1]) == offsetof(y) (using e.g. BOOST_STATIC_ASSERT). This will be the case on most compilers/platforms, but it never hurts to make sure. – j_random_hacker Apr 01 '09 at 02:09
  • @j_random_hacker : As it is a union, size of struct b is equal to size of array v (size of 2 int), there's no padding. So v[1] is y. No ? – doom Jan 27 '20 at 21:05
  • @doom: I believe a C++ compiler is permitted to add arbitrary padding in between arbitrary members of a struct, *except* before the very first. So while `sizeof v` is definitely `2*sizeof (int)`, it's technically possible (though unlikely) that there could be 42 bytes of padding between `x` and `y`, making `sizeof (b)` and therefore also `sizeof (a)` equal to `2*sizeof (int) + 42`, and `v[1]` doesn't line up with `y`. – j_random_hacker Jan 30 '20 at 00:53
  • @j_random_hacker thanks for this precision. So is it possible to add pure C++ directive to add some constraints ? (I understand that BOOST_STATIC_ASSERT is related to Boost) – doom Jan 30 '20 at 08:14
  • @doom: Well, these days standard C++ has `static_assert`, which you could use instead of `BOOST_STATIC_ASSERT` :) – j_random_hacker Jan 30 '20 at 21:58
6

Try this:

template<class T>
struct U1
{
    U1();
    T   v[2];
    T&  x;
    T&  y;
};

template<class T>
U1<T>::U1()
    :x(v[0])
    ,y(v[1])
{}

int main()
{
    U1<int>   data;

    data.x  = 1;
    data.y  = 2;
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • 1
    g++ gives sizeof(U1) as 12.. rather than 2. (g++ 4.0.1) – Michael Anderson Sep 27 '10 at 07:56
  • @Michael: Why do you think it should be 2? – Martin York Sep 27 '10 at 08:24
  • 4
    I'm saying that for something that is essentially providing access to two pieces of data, the resulting struct is expanded by a large degree by holding two additional pointers... That may be acceptable in some cases, and unacceptable in others .. Just a warning to those looking at this solution. – Michael Anderson Sep 27 '10 at 08:52
5

I've used something like this before. I'm not sure its 100% OK by the standard, but it seems to be OK with any compilers I've needed to use it on.

struct Vec2
{
  float x;
  float y;
  float& operator[](int i) { return *(&x+i); }
};

You can add bounds checking etc to operator[] if you want ( you probably should want) and you can provide a const version of operator[] too.

If you're concerned about padding (and don't want to add the appropriate platform specific bits to force the struct to be unpadded) then you can use:

struct Vec2
{
  float x;
  float y;
  float& operator[](int i) {
    assert(i>=0);
    assert(i<2);
    return (i==0)?x:y;
  }
  const float& operator[](int i) const {
    assert(i>=0);
    assert(i<2);
    return (i==0)?x:y;
  }
};
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • Some other comments were saying that `x` and `y` aren't guaranteed to be packed -- isn't `*(&x+i)` dangerous then? – mpen Nov 10 '21 at 08:01
  • 1
    Indeed if they're not packed correctly, then you need something else. In that case I'd use `float& operator[](int i) { return (i==0)?x:y; }` – Michael Anderson Nov 10 '21 at 09:38
3

I was looking for a similair thing and eventually came up with a solution.

I was looking to have a data storage object that I could use as both an array of values and as individual values (for end-user flexibility in writing Arduino libraries).

Here is what I came up with:

class data{
    float _array[3];

    public:
    float& X = _array[0];
    float& Y = _array[1];
    float& Z = _array[2];

    float& operator[](int index){
        if (index >= 3) return _array[0]; //Make this action whatever you want...
        return _array[index];
    }
    float* operator&(){return _array;}
};


int main(){
    data Test_Vector;
    Test_Vector[0] = 1.23; Test_Vector[1] = 2.34; Test_Vector[2] = 3.45;

    cout<<"Member X = "<<Test_Vector.X;
    cout<<"Member Y = "<<Test_Vector.Y;
    cout<<"Member Z = "<<Test_Vector.Z;

    float* vector_array = &Test_Vector;

    cout<<"Array = {"<<vector_array[0]<<", "<<vector_array[1]<<", "<<vector_array[2]<<"}";
}

Thanks to Operator overloading, we can use the data object as if was an array and we can use it for pass-by-reference in function calls (just like an array)!

If someone with More C++ experience has a better way of applying this end product, I would love to see it!

EDIT: Changed up the code to be more cross-platform friendly

Awbmilne
  • 136
  • 1
  • 3
  • Check the `sizeof(data)`. I suspect that its pretty large. Also because of the references, you'll need to implement copy and assign yourself. – Michael Anderson Nov 10 '21 at 09:42
2

Given your example:

union
{
    struct
    {
        Scalar x, y;
    };

    Scalar v[2];
};

As others have noted, in general, the standard does not guarantee that there will be no padding between x and y, and actually compilers inserting padding in structures is pretty common behavior.

On the other hand, with solutions like:

struct U
{
    int   v[2];
    int&  x;
    int&  y;
};

U::U()
    : x(v[0])
    , y(v[1])
{}

what I don't like mainly is the fact that I have to mention x, y twice. For cases where I have more than just a few elements (say 10), this becomes much less readable and harder to maintain - e.g. if you want to change the order of x,y then you have to change the indexes below too (well not mandatory but otherwise order in memory wouldn't match order of fields, which would not be recommended). Also, U can no longer be a POD since it needs a user-defined constructor. And finally, the x & y references consume additional memory.

Hence, the (acceptable for me) compromise I've come up with is:

struct Point
{
    enum CoordType
    {
        X,
        Y,
        COUNT
    };

    int coords[CoordType::COUNT];
};

typedef Point::CoordType PtCoord;

With this you can then do:

Point p;
for ( int i = 0; i < PtCoord::COUNT; i++ )
    p.coords[i] = 100;
std::cout << p.coords[PtCoord::X] << " " << p.coords[PtCoord::Y] << std::endl;

// 100 100

A bit sophisticated but I prefer this over the references suggestion.

Zuzu Corneliu
  • 1,594
  • 2
  • 15
  • 27
1

With C++11 you have anonymous unions and structs which just export their definitions to the enclosing scope, so you can do this:

typedef int Scalar;
struct Vector
{
    union
    {
        struct
        {
            Scalar x, y;
        };
        Scalar v[2];
    };
};
1

Depending on what "Scalar" is, yes, you can do that in C++. The syntax is almost exactly (maybe even exactly exactly, but I'm rusty on unions) what you wrote in your example. It's the same as C, except there are restrictions on the types that can be in the unions (IIRC they must have a default constructor). Here's the relevant Wikipedia article.

rmeador
  • 25,504
  • 18
  • 62
  • 103