提问人:Joel 提问时间:9/15/2023 更新时间:9/15/2023 访问量:88
使用成员变量创建数学向量,例如 XYZW 和 RGBA 的访问
Create mathematical vector with member variable like access for XYZW and RGBA
问:
我想创建一个类(不像,而是一个数学向量),它接受基础类型及其大小的模板参数。Vector
std::vector
访问其元素将如下所示:
// template <typename T, std::size_t N>
Vector<std::uint8_t, 3> vec{};
vec.x = 10;
vec.y = 12;
vec[2] = 14;
// if the size is 4:
// vec.w = 16;
我想要 ,如果它的大小是 3 和 4,则可以访问,但如果它的大小小于 3 或大于 4,则它们都不能访问,并且与 RGBA 相同。我不想使用方法(如),只需像普通成员变量一样访问它们。在所有情况下,我仍然希望能够拥有自己的下标运算符。.x
.y
.z
.w
.x()
我的想法是创建一个“基”类,该类将包含所有通用方法,然后从中派生。Vector
template <typename T, std::size_t N>
struct Vector {
};
template <typename T>
struct Vector<T, 3> : Vector<T, 1> {
union {
T x, r;
};
union {
T y, g;
};
union {
T z, b;
};
};
template <typename T>
struct Vector<T, 4> : Vector<T, 3> {
union {
T w, a;
};
};
但是,要实现某些方法(例如下标运算符),我需要在“基”类中为数据(如)创建一个数组。这将使数组和联合成为内存中的不同对象,并且根本不起作用。T data[N];
我现在想知道是否有一些方法可以实现所需的行为,而无需创建一些额外的类(例如)并为那些大小从 2 到 4 的类提供重复代码。Vector3<T>
Vector
答:
1赞
Jan Schultke
9/15/2023
#1
您不一定需要将数组作为成员。你只需要实现,所有其他运算符(如、等)都变得很容易实现:operator[]
+
-
#include <cstddef>
#include <utility>
template <typename T, std::size_t N>
struct Vector {
const T& operator[](std::size_t i) const {
// obtain pointer to the bytes of this object
auto bytes = reinterpret_cast<const std::byte*>(this);
// access the T object which is presumably stored at this byte offset
return *reinterpret_cast<const T*>(bytes + i * sizeof(T));
}
T& operator[](std::size_t i) {
// One of the few safe use cases of const_cast:
// Add an unnecessary const and remove it with const_cast.
return const_cast<T&>(std::as_const(*this)[i]);
}
};
这种实现假设所有的 s 都布置在一个数组中,这是一个合理的假设。
请注意,这是无效的,因为不是 的数组,并且对此类指针进行指针算术操作将是未定义的行为。T
struct
reinterpret_cast<T*>
Vector
T
您可以通过以下方式确保布局正确:
static_assert(sizeof(Vector<int, 4>) == sizeof(int[4]));
如果您的类型具有 (但是实现,则其他操作将变得易于实现。operator[]
template <typename T, std::size_t N>
Vector<T, N> operator+(const Vector<T, N>& a, const Vector<T, N>& b) {
Vector<T, N> result;
for (std::size_t i = 0; i < N; ++i) {
result[i] = a[i] + b[i];
}
return result;
}
指针算术注意事项std::byte
从技术上讲,对对象的底层序列进行指针算术是未定义的行为,但这更像是标准的缺陷,没有编译器强制执行。看:std::byte
- 通过将对象指针转换为“char *”,然后执行“*(member_type*)(指针 + 偏移量)”来访问成员是否是 UB?*
- P1839R5:访问对象表示(C++建议)
- P1839 访问对象表示形式 (GitHub)
评论
0赞
Joel
9/15/2023
谢谢你的这个想法。当我以与我的问题类似的方式实现我的 s(从 3 继承 4,但从 0 继承 3)并重载/指定(不确定术语)那些具有 1、2 和高于 4 的值时,我可能会得到想要的结果。我明天会研究这个问题。Vector
0赞
Mooing Duck
9/15/2023
我宁愿切换未定义的行为 coliru.stacked-crooked.com/a/a61fc448d08a102f
0赞
Jan Schultke
9/15/2023
@MooingDuck a 需要为每个长度执行额外的工作,并且不会产生最佳的 codegen。请参见 godbolt.org/z/zPdE3TMs5。这是一个可能的解决方案,只是远非最佳。switch
0赞
Jan Schultke
9/15/2023
@MooingDuck它通过内联和持续传播得到优化,因为这是一个微不足道的例子。你可以很容易地想出一个不那么平凡的例子:godbolt.org/z/bsYWx4Wrb 使用函数获得完全幼稚的代码生成,而使用函数的函数获得矢量化操作。您可能会看到基准测试存在显着差异。(tbf 我认为 GCC 在这里绊倒了;在这种特定情况下,矢量化版本是否更好并不明显)switch
reinterpret_cast
评论
[]
operator[]
data[N]
x
y
z
w
union