[英]Member variable alias in class template specialization
让我们假设我正在编写一个Vector
模板类来表示N维空间中的点和向量。 类似于以下内容:
template <typename T, int N>
struct Vector
{
T data[N];
// ...
};
让我们进一步假设,无论出于何种原因,我希望用户能够在较小向量的情况下访问具有有意义名称的data
,例如通过使用vx
或vy
而不是v.data[0]
和v.data[1]
。
我还有两个额外的限制。
x
或y
分量的访问写为函数调用(例如,它应该是vx
,而不是vx()
)。 sizeof(Vector<T, N>) == N * sizeof(T)
。 我查看了不同的可能方法,包括成员变量引用, 标记分派甚至CRTP,但它们都没有满足我的所有要求。
甚至可以创建这样的别名吗? 如果是的话,怎么能这样做呢?
(这不是一个答案,它是一个带有代码示例的注释,它不适合作为注释,并且如果它可以填充到注释中,则格式不正确。)
你可以走向另一个方向,并将矢量表示为一堆字段,然后将索引getter / setter映射到每个字段吗?
取出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';
}
如果允许宏 ,那么它似乎是可行的。
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是标准布局类型 ,我想到了一个更高效的operator [],具有以下实现:
#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); \
} \
} \
}
(BAD)尝试: http : //coliru.stacked-crooked.com/a/d367e770f107995f
对于所提出的实现,任何带有DIMENSIONS> 1的Vector 都不是标准布局类 (即使是T), 因为它在基类和派生类中都有成员 :
10.1 [class.prop]
[3]如果是,则S类是标准布局类:...
[3.6]该类中的所有非静态数据成员和位字段及其基类首先在同一个类中声明...
因此,上面的优化尝试具有未定义的行为 - 编译器没有义务按顺序将成员的地址保留在继承层次结构中。
初始解决方案仍然有效。
这是可能的解决方案(虽然我认为这是不好的做法,并不确定是否便携):
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
更新:并且要明确定义,您可以执行以下操作:
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]
似乎实际上不是布局兼容的(见这里 )。 尽管如此,这种模式似乎在各种库中使用,用于实现简单的矢量类型 - example1(GLM) , example2 video , example3
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.