如何同时更新结构的多个字段?

How to update multiple fields of a struct simultaneously?

假设我有一个结构

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

注意 sizeof(Vector3) 必须保持不变。

编辑:我对没有 setter 的解决方案很感兴趣。

不是让我们创建该结构的实例 Vector3 pos 。我怎样才能实现我的结构,这样我就可以得到这样的东西 pos.xy = 10 // updates x and y or pos.yz = 20 // updates y and z or pos.xz = 30 // updates x and z?

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?

只需添加必要的 class 成员函数即可:

struct Vector3 {
    float x;
    float y;
    float z;
    void update_xy(float value) { x = y = value; }
    void update_yz(float value) { y = z = value; }
    void update_xz(float value) { x = z = value; }
};

简单的方法就是提供setters给你要设置的组合:

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);
}

如果您需要 sizeof(Vector3) 保持不变,那是唯一的方法。


只是“为了好玩”,这就是您如何从字面上获得 pos.set_xy = 20;

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;
}

然而,它有严重的缺点。首先,它的大小几乎可以是原来 Vector3 的两倍。而且,因为two_setter存储的是引用,所以Vector3不能被复制。如果它将存储指针,则可以进行复制,但需要更多代码才能正确完成。

或者,可以提供一个 xy 方法,returns 一个分配两个成员的代理。但我不会详细说明,因为 pos.xy() = 3; 看起来很奇怪,与 pos.xy(3) 没有任何优势,你真的应该提供一个 setter (或者只是依靠用户在他们进行两次分配时想做两个作业 ;).

TL;DR 使用方法而不是尝试获取 C++ 不支持的语法。

这是一个具有所需语法的解决方案,并且不会增加 class 的大小。它在技术上是正确的,但相当复杂:

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;
};

另一个依赖于此处实现的 owner_ofhttps://gist.github.com/xymopen/352cbb55ddc2a767ed7c5999cfed4d31 这可能取决于某些特定于技术实现(可能未定义)的行为:

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;
};

可以在 Vector3 中创建一个空结构,其中 operator=() 设置外部结构的变量。当然,要使变量本身真正不使用 space,您必须使用 [[no_unique_address]],它仅在 C++20 之后可用。但这里有一个它可能如何工作的例子:

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;
};

godbolt.org 上查看 运行。

由于您的类型是标准布局,我认为根据 C++ 标准,唯一合法 方法是使用包含 sub 的 union -具有自定义 operator= 定义的对象。

使用 union,您可以查看活动成员的 公共初始序列 ,前提是所有类型都是标准布局类型。因此,如果我们精心制作一个共享相同公共成员的对象(例如 3 个 float 对象,顺序相同),那么我们可以在不违反严格别名的情况下在它们之间“混合”。

为了实现这一点,我们需要创建一组成员,这些成员都具有相同顺序、标准布局类型的相同数据。

作为一个简单的例子,让我们创建一个基本的代理类型:

template <int...Idx>
class Vector3Proxy
{
public:

    // ...

    template <int...UIdx, 
              typename = std::enable_if_t<(sizeof...(Idx)==sizeof...(UIdx))>>
    auto operator=(const Vector3Proxy<UIdx...>& other) -> Vector3Proxy&
    {
        ((m_data[Idx] = other.m_data[UIdx]),...);
        return (*this);
    }

    auto operator=(float x) -> Vector3Proxy&
    {
        ((m_data[Idx] = x),...);
        return (*this);
    }

    // ...

private:

    float m_data[3];
    template <int...> friend class Vector3Proxy;
};

在这个例子中,没有使用 m_data 的所有成员——但它们的存在是为了满足“公共初始序列”的要求,这将允许我们通过其他标准布局查看它union.

中的类型

这可以根据需要建立; float 单分量运算符的转换、算术支持等

有了这样的类型,我们现在可以从这些代理类型中构建 Vector3 个对象

struct Vector3
{
    union {
        float _storage[3]; // for easy initialization
        Vector3Proxy<0> x;
        Vector3Proxy<1> y;
        Vector3Proxy<2> z;
        Vector3Proxy<0,1> xy;
        Vector3Proxy<1,2> yz;
        Vector3Proxy<0,2> xz;
        // ...
    };
};

然后该类型可以很容易地用于一次分配给多个值:

Vector3 x = {1,2,3};

x.xy = 5;

或者将一个部分的组件分配给另一个:

Vector3 a = {1,2,3};
Vector3 b = {4,5,6};

a.xy = b.yz; // produces {5,6,3}

Live Example

此解决方案还确保 sizeof(Vector3) 不会更改,因为所有代理对象的大小都相同。


注意: 在 C++ 中使用带有匿名 structunion 是无效的,尽管一些编译器支持它。因此,尽管重写它可能很诱人:

union {
    struct {
        float x;
        float y;
        float z;
    }; // invalid, since this is anonymous
    struct {
        ...
    } xy;
}

这在标准 C++ 中无效,也不是可移植的解决方案。