Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update multiple fields of a struct simultaneously?

Let's say I have a struct

struct Vector3 {
    float x;
    float y;
    float z;
};

Note that sizeof(Vector3) must remain the same.

EDIT: I am interested in solutions without setters.

Not let's create an instance of that struct Vector3 pos . How can I implement my struct so I can have something like this pos.xy = 10 // updates x and y or pos.yz = 20 // updates y and z or pos.xz = 30 // updates x and z?

like image 442
Hrant Nurijanyan Avatar asked Nov 26 '20 18:11

Hrant Nurijanyan


3 Answers

Here is a solution that has the desired syntax, and doesn't increase the size of the class. It is technically correct, but rather convoluted:

union Vector3 {
    struct {
        float x, y, z;
        auto& operator=(float f) { x = f; return *this; }
        operator       float&() &        { return  x; }
        operator const float&() const &  { return  x; }
        operator       float () &&       { return  x; }
        float* operator&()               { return &x; }
    } x;
    
    struct {
        float x, y, z;
        auto& operator=(float f) { y = f; return *this; }
        operator       float&() &        { return  y; }
        operator const float&() const &  { return  y; }
        operator       float () &&       { return  y; }
        float* operator&()               { return &y; }
    } y;
    
    struct {
        float x, y, z;
        auto& operator=(float f) { z = f; return *this; }
        operator       float&() &        { return  z; }
        operator const float&() const &  { return  z; }
        operator       float () &&       { return  z; }
        float* operator&()               { return &z; }
    } z;
    
    struct {
        float x, y, z;
        auto& operator=(float f) { x = y = f; return *this; }
    } xy;
    
    struct {
        float x, y, z;
        auto& operator=(float f) { y = z = f; return *this; }
    } yz;
    
    struct {
        float x, y, z;
        auto& operator=(float f) { z = x = f; return *this; }
    } zx;
};

Another which relies on owner_of implemented here: https://gist.github.com/xymopen/352cbb55ddc2a767ed7c5999cfed4d31 which probably depends on some technically implementation specific (possibly undefined) behaviour:

struct Vector3 {
    float x;
    float y;
    float z;
    
    [[no_unique_address]]
    struct {
        auto& operator=(float f) {
            Vector3* v = owner_of(this, &Vector3::xy);
            v->x = v->y = f;
            return *this;
        }
    } xy;
    [[no_unique_address]]
    struct {
        auto& operator=(float f) {
            Vector3* v = owner_of(this, &Vector3::yz);
            v->y = v->z = f;
            return *this;
        } 
    } yz;
    [[no_unique_address]]
    struct {
        auto& operator=(float f) {
            Vector3* v = owner_of(this, &Vector3::zx);
            v->z = v->x = f;
            return *this;
        }
    } zx;
    [[no_unique_address]]
    struct {
        auto& operator=(float f) {
            Vector3* v = owner_of(this, &Vector3::zx);
            v->x = v->y = v->z = f;
            return *this;
        }
    } xyz;
};
like image 196
eerorika Avatar answered Oct 06 '22 00:10

eerorika


The simple way is to provide setters for the combinations you want to set:

struct Vector3 {
    float x = 0;
    float y = 0;
    float z = 0;
    void set_xy(float v) {
        x = v;
        y = v;
    }
};

int main(){
    Vector3 pos;
    pos.set_xy(42);
}

And if you need sizeof(Vector3) to stay the same, thats the only way.


Just "for fun" this is how you can get pos.set_xy = 20; literally:

struct two_setter {
    float& one;    
    float& two;
    void operator=(float v){
        one = v;
        two = v;
    }
};

struct Vector3 {
    float x = 0;
    float y = 0;
    float z = 0;
    two_setter set_xy{x,y};
};

int main(){
    Vector3 pos;
    pos.set_xy = 42;
}

However, it has severe downsides. First it can have almost twice the size of the original Vector3. Moreover, because the two_setter stores references, Vector3 cannot be copied. If it would store pointers, copying would be possible, but then even more code would be required to get it right.

Alternatively it is possible to provide a xy method that returns a proxy that assigns the two members. But I am not going into detail, because pos.xy() = 3; looks really odd, has no advantage to pos.xy(3) and you really should provide a setter (or just rely on the user making two assignments when they want to make two assignments ;).

TL;DR Use a method instead of trying to get a syntax that C++ does not support out of the box.

like image 34
463035818_is_not_a_number Avatar answered Oct 06 '22 00:10

463035818_is_not_a_number


It is possible to create an empty struct inside Vector3 with an operator=() that sets the variables of the outer struct. Of course for a variable to really take no space itself, you have to use [[no_unique_address]], which is only available since C++20. But here is an example of how it might work:

struct Vector3 {
    [[no_unique_address]] struct {
        auto &operator=(float val) {
            Vector3 *self = (Vector3 *)(this);
            self->x = val;
            self->y = val;
            return *this;
        }
    } xy;

    // Add similar code for xz and yz

    float x;
    float y;
    float z;
};

See it running on godbolt.org.

like image 41
G. Sliepen Avatar answered Oct 06 '22 00:10

G. Sliepen