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

2022-03-24 00:00:00 field struct c++ accessor

假设我有一个结构

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

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

编辑:我对没有设置器的解决方案感兴趣。

我们不创建该结构实例Vector3 pos。如何实现我的结构,以便我可以拥有类似pos.xy = 10 // updates x and ypos.yz = 20 // updates y and zpos.xz = 30 // updates x and z的内容?


解决方案

由于您的类型是标准布局,我认为按照C++标准,执行此操作的唯一合法方法是使用包含具有自定义operator=定义的子对象的union

使用union,您可以查看活动成员的公共-初始序列,前提是所有类型都是标准布局类型。因此,如果我们仔细创建共享相同公共成员的对象(例如,3个float对象按相同顺序),则可以在不违反严格别名的情况下在它们之间交换(&q;swizzle&q;)。

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

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

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的所有成员,但它们的存在是为了满足";common-初始序列";要求,这将允许我们通过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++中,将union与匿名struct一起使用是无效的,尽管有些编译器支持它。因此,尽管重写这段代码可能很诱人:

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

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

相关文章