如何同时更新结构的多个字段?
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_of
:https://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}
此解决方案还确保 sizeof(Vector3)
不会更改,因为所有代理对象的大小都相同。
注意: 在 C++ 中使用带有匿名 struct
的 union
是无效的,尽管一些编译器支持它。因此,尽管重写它可能很诱人:
union {
struct {
float x;
float y;
float z;
}; // invalid, since this is anonymous
struct {
...
} xy;
}
这在标准 C++ 中无效,也不是可移植的解决方案。
假设我有一个结构
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
orpos.yz = 20 // updates y and z
orpos.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_of
:https://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}
此解决方案还确保 sizeof(Vector3)
不会更改,因为所有代理对象的大小都相同。
注意: 在 C++ 中使用带有匿名 struct
的 union
是无效的,尽管一些编译器支持它。因此,尽管重写它可能很诱人:
union {
struct {
float x;
float y;
float z;
}; // invalid, since this is anonymous
struct {
...
} xy;
}
这在标准 C++ 中无效,也不是可移植的解决方案。