数组元素的 c++ 别名

c++ Alias for an array element

本文关键字:别名 c++ 数组元素      更新时间:2023-10-16

我有一个(列)Vector类,其中包含一个可以访问的值数组,如下所示:

Vec<int, 4> v();
v[0] = -2; // <- set first value to -2
v[1] = 1; // <- set second value to 1
....

但这是我的问题:如何为v[0], v[1], v[2], v[3]创建别名?我想将前 4 个值定义为v.x, v.y, v.z, v.w

Vec<int, 4> v();
v.x = -2; // <- set first value to -2
v.y = 1; // <- set second value to 1
v.z = 4; // <- set third value to 4
v.w = 2; // <- set fourth value to 2

我应该能够分配读取值,并且我不希望它们看起来像一个函数,因此访问第一个值时如下所示:

Vec<int, 4> v();
v.x() = -2; // <- set first value to -2

不好。最重要的是,向量类是模板化的,x应该只为dimensions >= 1定义,y只为dimenions >= 2定义。等等...我如何实现这一点?

编辑:Vector 类与 std::vector 无关,它是一个类似于数组的数学向量,因为它的大小是固定的,仅用于数学运算。(将矢量重命名为 Vec)。

我尝试过的:

矩阵类:

template <typename T, size_t ROWS, size_t COLS>
class Matrix {
public:
T& operator[] (size_t idx) {return m_matrix[idx];}
T operator[] (size_t idx) const {return m_matrix[idx];}
private:
m_matrix[ROWS * COLS]
};

向量类:

template <typename T, size_t N>
class Vec: public Matrix<T, 1, N>{
public:
T& x() {return (*this)[0];}
T x() const {return (*this)[0];}
T& y() {return (*this)[1];}
T y() const {return (*this)[1];}
T& z() {return (*this)[2];}
T z() const {return (*this)[2];}
T& w() {return (*this)[3];}
T w() const {return (*this)[3];}
};

这有效,如果没有为此维度定义函数,我可以轻松地使用 enable_if 来删除函数,但这在语法上并不令人满意。我尝试使用引用:

template <typename T, size_t N>
class Vec: public Matrix<T, N, 1>{
public:
T& x = (*this)[0];
T& y = (*this)[1];
T& z = (*this)[2];
T& w = (*this)[3];
};

但这不起作用,它不会给我一个错误,但它也没有正确设置值,当我在设置它们未定义后访问它们时。

编辑nr 2:可能只是存在一个更简单的解决方案,当我最后一次尝试使用Visual Studio Community 2015的默认编译器编译引用时,它就可以工作了。但是当我使用 GNU GCC 编译器在 Code::Blocks 中编译它时,它就没有了。标准怎么说?我的解决方案是否允许使用引用,哪个编译器是错误的?

这个:

template <typename T, int D> struct Vec;
// You have to manually specialize for all needed sizes
template <typename T> struct Vec<T, 4>
{
T x, y, z, w;
T &operator[](int index)
{
switch (index)
{
default: // throw or something?
case 0: return x;
case 1: return y;
case 2: return z;
case 3: return w;
}
}
const T &operator[](int index) const
{
switch (index)
{
default: // throw or something?
case 0: return x;
case 1: return y;
case 2: return z;
case 3: return w;
}
}
};

索引switch不是最佳的,但至少它是明确定义的。

对于矩阵,我更喜欢使用Vec<Vec<T, Height>, Width>,这使得mat[x][y]符号起作用。(如果需要,可以交换xy

如果你接受 C++14 的解决方案,我建议为xyzw创建一个模板索引包装器,引用T变量

template <typename T, std::size_t>
struct wrapper
{ wrapper (T const &) {} };
template <typename T>
struct wrapper<T, 0U>
{ T & x; };
template <typename T>
struct wrapper<T, 1U>
{ T & y; };
template <typename T>
struct wrapper<T, 2U>
{ T & z; };
template <typename T>
struct wrapper<T, 3U>
{ T & w; };

接下来是必须在索引包装器之前继承的std::array包装器

template <typename T, std::size_t N>
struct arrayWrp
{ std::array<T, N> arr {}; };

现在,您可以定义一个帮助程序struct VecH

如下所示
template <typename T, std::size_t ... Is>
struct VecH<T, std::index_sequence<Is...>>
: public arrayWrp<T, sizeof...(Is)>, public wrapper<T, Is>...
{
using arrayWrp<T, sizeof...(Is)>::arr;
VecH () : arrayWrp<T, sizeof...(Is)>{}, wrapper<T, Is>{ arr[Is] }...
{ }
T & operator[] (std::size_t i)
{ return arr[i]; }
T const & operator[] (std::size_t i) const
{ return arr[i]; }
};

继承自arrayWrp和所有需要wrapper<T, Is>,并将xyzw引用到arr[0]arr[1]arr[2]arr[3]

所以Vec成为

template <typename T, std::size_t N>
struct Vec : public VecH<T, std::make_index_sequence<N>>
{ };

以下是完整的工作示例

#include <array>
#include <iostream>
#include <type_traits>
template <typename T, std::size_t>
struct wrapper
{ wrapper (T const &) {} };
template <typename T>
struct wrapper<T, 0U>
{ T & x; };
template <typename T>
struct wrapper<T, 1U>
{ T & y; };
template <typename T>
struct wrapper<T, 2U>
{ T & z; };
template <typename T>
struct wrapper<T, 3U>
{ T & w; };
template <typename T, std::size_t N>
struct arrayWrp
{ std::array<T, N> arr {}; };
template <typename, typename>
struct VecH;
template <typename T, std::size_t ... Is>
struct VecH<T, std::index_sequence<Is...>>
: public arrayWrp<T, sizeof...(Is)>, public wrapper<T, Is>...
{
using arrayWrp<T, sizeof...(Is)>::arr;
VecH () : arrayWrp<T, sizeof...(Is)>{}, wrapper<T, Is>{ arr[Is] }...
{ }
T & operator[] (std::size_t i)
{ return arr[i]; }
T const & operator[] (std::size_t i) const
{ return arr[i]; }
};
template <typename T, std::size_t N>
struct Vec : public VecH<T, std::make_index_sequence<N>>
{ };
int main ()
{ 
Vec<int, 4U>  v4;
v4.x = 1;
v4.y = 2;
v4.z = 3;
v4.w = 4;
std::cout << "v4: ";
for ( auto ui = 0U ; ui < 4U ; ++ui )
std::cout << ' ' << v4[ui];
std::cout << std::endl;
Vec<int, 5U>  v5;  // also over 4
Vec<int, 3U>  v3;
v3.x = 10;
v3.y = 20;
v3.z = 30;
// v3.w = 40;  // compilation error
}

如果您不喜欢使用VecH帮助程序struct,则可以使用部分专用化和默认为std::make_index_sequence<N>的模板参数,如下所示

template <typename, std::size_t N, typename = std::make_index_sequence<N>>
struct Vec;
template <typename T, std::size_t N, std::size_t ... Is>
struct Vec<T, N, std::index_sequence<Is...>>
: public arrayWrp<T, N>, public wrapper<T, Is>...
{
using arrayWrp<T, sizeof...(Is)>::arr;
Vec () : arrayWrp<T, sizeof...(Is)>{}, wrapper<T, Is>{ arr[Is] }...
{ }
T & operator[] (std::size_t i)
{ return arr[i]; }
T const & operator[] (std::size_t i) const
{ return arr[i]; }
};

但我不知道这是否是一个好主意:有人可以尝试按如下方式使用Vec

Vec<int, 3U, std::index_sequence<0, 2, 5>>  v;

一种完全不同的方法是重载括号运算符,或者更简单地使用命名空间和定义的常量:

namespace xyzw {
constexpr const size_t X = 0;
constexpr const size_t Y = 1;
constexpr const size_t Z = 2;
constexpr const size_t W = 3;
}
namespace rgba {
constexpr const size_t R = 0;
constexpr const size_t G = 1;
constexpr const size_t B = 2;
constexpr const size_t A = 3;
}

现在,您可以通过以下方式使用它:

template<class V>
typename V::value_type snorm3(const V & v) {
using namespace xyzw;
if (v[W] == 0) return 0;
return (v[X] * v[X] + v[Y] * v[Y] + v[Z] * v[Z]) / (v[W] * v[W]);
}
int test() {
std::vector <int> t{ 0,1,2,3 };
return snorm3 (t);
}

作为一个未经测试的扩展@max66我们可以以一种不同的方式定义包装器:

template <typename T, size_t n, class V = std::vector<T> >
class wrapper {
wrapper(V & vec):v(vec) {}
operator T & () { return v[n]; }
operator = (const T & o) { v[n] = o; }
protcted:
V & v;
}
template <typename T, class V = std::vector<T> > 
class Vec: public V {
public:
wrapper<T,0,V> x;
wrapper<T,1,V> y;
wrapper<T,2,V> z;
wrapper<T,3,V> w;
Vec(): x(*this), y(*this), z(*this), w(*this) {} 
} 

在这里,包装器引用向量(或使用的任何内容)并在访问时进行索引。这可能会更慢,因为 x、y、z、w 不是值,而是它们的包装器,您可能需要为它们定义更多的运算符。

也许是这样的,假设Vec<T>::operator[]()返回T&并假设Vec中的元素按顺序位于内存中:

struct XYZW {
int x, y, z, w;
};
Vec<int, 4> v;
v[0] = 1;
v[1] = 2;
v[2] = 3;
v[3] = 4;

auto p = reinterpret_cast<XYZW*>(&v[0]);
// use it:
std::cout << p->x << ',' << p->y << ',' << p->z << ',' << p->w << std::endl;
// compare with Vec:
std::cout << v[0] << ',' << v[1] << ',' << v[2] << ',' << v[3] << std::endl;

也许对你来说为时已晚,但无论如何都会有其他人在寻找解决方案。

我认为这样的事情可以满足你的愿望:

#include <array>
#include <cstdint>
#include <type_traits>
template <typename T, std::size_t N>
struct vec {
static_assert(N <= 4, "Dimension of vec must be 4 or less.");
struct EmptyType {};
using T1 = typename std::conditional_t<N >= 1, T, EmptyType>;
using T2 = typename std::conditional_t<N >= 2, T, EmptyType>;
using T3 = typename std::conditional_t<N >= 3, T, EmptyType>;
using T4 = typename std::conditional_t<N >= 4, T, EmptyType>;
union {
struct {
T1 x;
T2 y;
T3 z;
T4 w;
};
std::array<T, N> data;
};
vec(const std::array<T, N>& data):
this->data(data) {} 
// ...
};

并集中的位置与数组中的位置相同,因此 [0] 是 x,依此类推。

在VS v17.8-preview.1上使用c ++最新版本进行了测试。

你可以做这样的事情:

// General case uses an array
template<class T, std::size_t N>
class VectorData
{
private:
T m_data[N];
public:
T& operator[](int i) { return m_data[i]; }
const T& operator[](int i) const { return m_data[i]; }
};
// Specializations for various N (4 shown here)
template<class T>
class VectorData<T, 4>
{
public:
T x, y, z, w;
T& operator[](int i) { return (&x)[i]; }  // WARNING, see note below
const T& operator[](int i) const { return (&x)[i]; }
};

template<class T, std::size_t N>
struct Vector : public VectorData<T, N>
{
// your other Vector stuff here
};

注意:正如下面的一位评论者正确指出的那样,这假设数组元素在内存中的布局与变量列表(iow、T[4]struct { T x,y,z,w; }布局兼容)完全相同,以便&x[i]部分正常工作。该标准不保证这一点,因此此代码将产生未定义的行为。在实践中,这很好,并且以这种方式这样做的性能更高。如果您需要一个可移植的、符合标准的实现,您可以选择在VectorData::operator[]中使用switch,正如另一个答案所建议的那样。生成的代码差异可以在这里看到。

如果你真的需要 Vector 从 Matrix 派生,这样的事情仍然是可能的。一般的想法只是将存储和功能分离。您可以创建一些具有所有功能的泛型 Matrix 类,该类具有用于存储的额外模板参数。然后,Vector 可以只提供自己的存储类型。

像这样:

// Generic matrix storage
template<class T, std::size_t N>
class MatrixData
{
private:
T m_data[N];
public:
T& operator[](int i) { return m_data[i]; }
const T& operator[](int i) const { return m_data[i]; }
};
// Generic matrix class
template<class T, std::size_t ROWS, std::size_t COLS, class Storage = MatrixData<T, ROWS*COLS>>
class Matrix : public Storage
{
// Matrix functionality here
};

// Specialized storage for Vectors, generic version
template<class T, std::size_t N>
class VectorData : public MatrixData<T, N> { };
// Specialized storage for Vector<T, 4>
template<class T>
class VectorData<T, 4>
{
public:
T x, y, z, w;
T& operator[](int i) { return (&x)[i]; }
const T& operator[](int i) const { return (&x)[i]; }
};
template<class T, std::size_t N>
struct Vector : public Matrix<T, N, 1, VectorData<T, N>>
{
// your other stuff here
};