让我们假设我正在编写一个Vector
模板类来表示N维空间中的点和向量。类似于以下内容:
template <typename T, int N>
struct Vector
{
T data[N];
// ...
};
让我们进一步假设,无论出于何种原因,我都希望用户在较小的矢量(例如)的情况下能够使用有意义的名称访问data
。通过使用v.x
或v.y
而不是v.data[0]
和v.data[1]
。
我还有两个约束。
x
或y
组件的访问不应写为函数调用(例如,应为v.x
,而不是v.x()
)。sizeof(Vector<T, N>) == N * sizeof(T)
。我研究了各种可能的方法,包括成员变量引用,标签分发,甚至还有 CRTP ,但是没有一个方法满足我的所有要求。
甚至可以创建这样的别名吗?如果是的话,怎么办呢?
答案 0 :(得分:2)
(这不是答案,它是带有代码示例的注释,不适合作为注释,并且如果可以将其填充到注释中,格式也不正确。)
您可以走另一个方向,将向量表示为一堆字段,然后将索引获取器/设置器映射到每个字段吗?
取出N模板参数来简化问题:
#include <iostream>
#include <stdexcept>
template <typename T>
struct Vector3
{
T x;
T y;
T z;
T operator[](int i) const
{
switch(i)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
default:
throw std::out_of_range("out of range");
}
}
T& operator[](int i)
{
switch(i)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
default:
throw std::out_of_range("out of range");
}
}
};
int main()
{
Vector3<float> v;
v.x = 1.0f;
v[1] = 2.0f;
v.z = 3.0f;
std::cout << v[0] << " " << v.y << " " << v[2] << '\n';
}
答案 1 :(得分:1)
如果允许宏,那么它似乎可行。
int main() {
Vector<int, 4> vec;
vec[0] = 1; // same as: vec.t1 = 1;
vec[1] = 2; // same as: vec.t2 = 2;
vec[2] = 3; // same as: vec.t3 = 3;
vec[3] = 4; // same as: vec.t4 = 4;
std::cout << vec.t1 + vec.t2 + vec.t3 + vec.t4; // 10
}
要实现上述目标:
#define VAR_NAME(num) t##num
#define DefineVector(num) \
template<typename T> \
struct Vector<T, num> : Vector<T, num-1> { \
T VAR_NAME(num); \
T& operator[](int index) { \
if(index == num-1) return VAR_NAME(num); \
return Vector<T, num-1>::operator[](index); \
} \
}
template<typename T, size_t N>
struct Vector;
template<typename T>
struct Vector<T, 1> {
T t1;
T& operator[](int index) {
// in case index != 0 this is UB
return t1;
}
};
DefineVector(2);
DefineVector(3);
DefineVector(4);
// TODO:
// replace 3 declarations above with a single *DefineVectorsRecursively(4);*
// by using recursive macros
// see: https://stackoverflow.com/questions/12447557/can-we-have-recursive-macros
// leaving this as a further exercise...
http://coliru.stacked-crooked.com/a/42625e9c198e1e58
编辑:添加了运算符[],以解决评论中提出的问题。
OP要求字段具有更好的名称,例如x,y,z。
这是一个挑战。但是,宏又再次发挥了作用:
int main() {
Vector<int, 3> vec;
vec[0] = 1;
vec[1] = 2;
vec[2] = 3;
std::cout << vec.x + vec.y + vec.z; // 6
}
使用以下代码:
#include <boost/preprocessor/variadic/size.hpp>
template<typename T, size_t DIMENSIONS>
struct Vector;
#define DefineVector(VAR, ...) \
template<typename T> \
struct Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__) + 1> \
: Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)> { \
T VAR; \
T& operator[](int index) { \
if(index == BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)) return VAR; \
return Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)>::operator[](index); \
} \
}
#define DefineVector1(VAR) \
template<typename T> \
struct Vector<T, 1> { \
T VAR; \
T& operator[](int index) { \
/* in case index != 0 this is UB */ \
return VAR; \
} \
}
DefineVector1(x);
DefineVector(y, x);
DefineVector(z, y, x);
// TODO: create recursive macro for DefineVector(z, y, x)
// that will create the two above recursively
代码:http://coliru.stacked-crooked.com/a/2550eede71dc9b5e
在T为 标准布局类型 的情况下,我想到了一种更高效的运算符[],并实现以下操作:
#define DefineVector(VAR, ...) \
template<typename T> \
struct Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__) + 1> \
: Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)> { \
T VAR; \
T& operator[](int index) { \
if constexpr(std::is_standard_layout_v<T>) { \
return *(&VAR - (BOOST_PP_VARIADIC_SIZE(__VA_ARGS__) - index)); \
} else { \
if(index == BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)) return VAR; \
return Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)>::operator[](index); \
} \
} \
}
(不良)尝试:http://coliru.stacked-crooked.com/a/d367e770f107995f
对于所提供的实现,任何DIMENSIONS> 1的Vector都是 不是标准布局类 (即使T为because it has members both in base class and in the derived class:
10.1 [class.prop]
[3]如果满足以下条件,则S类为标准布局类:...
[3.6]在类及其基类中具有所有非静态数据成员和位字段 首先在同一类中声明的类...
因此,上面的优化尝试具有未定义的行为-编译器没有义务按继承顺序保留成员在继承层次结构中的地址。
初始解决方案仍然有效。
答案 2 :(得分:-1)
这里是可能的解决方案(尽管我认为这是不好的做法,并且不确定是否可移植):
template <typename T, int N>
union Vector
{
struct { T x, y, z; };
T data[N];
};
以下是发生的示例:
int main() {
Vector<int, 10> vec;
vec.x = 100;
vec.y = 200;
vec.z = 300;
vec.data[3] = vec.data[2] + 100;
printf("%d %d %d %d\n", vec.data[0], vec.data[1], vec.data[2], vec.data[3]);
printf("size = %d\n", (int) sizeof(vec));
return 0;
}
Output:
100 200 300 400
size = 40
更新:要进行此well defined,您可以执行以下操作:
template <typename T, int N> union Vector;
template <typename T> union Vector<T, 1> {
struct { T x; };
T data[1];
};
template <typename T> union Vector<T, 2> {
struct { T x, y; };
T data[2];
};
template <typename T> union Vector<T, 3> {
struct { T x, y, z; };
T data[3];
};
template <typename T> union Vector<T, 4> {
struct { T x, y, z, w; };
T data[4];
};
只需确保struct
是标准版式(即适用于T = int,float,double等)。
更新2:请注意,以上内容仍可能是UB,因为T x, y, z
和T data[3]
似乎实际上与布局不兼容(请参阅here) 。尽管如此,该模式似乎仍在各种库中用于实现简单的向量类型-example1 (GLM),example2 video,example3