我在这里滥用了模板吗?
Have I misused templates here?
我试图为一个网格类进行通用编程。 (我需要 CPU 转换来变形网格,这就是为什么这不会在 GPU 中完成,以防您想问)
Mesh<T>
包含 3D 形状的顶点,以及面绑定(哪些顶点连接在一起形成三角形)。
这里T
是顶点的类型,某些类型的模型具有PNCT类型(位置,法线,颜色,texcoord),而其他模型只是PC(位置,颜色)。
struct VertexPNCT {
Vector3 pos, normal ;
Vector4 color ;
Vector2 tex ;
} ;
struct VertexPC {
Vector3 pos ;
Vector4 color ;
} ;
当然,所有顶点都有一个位置!
但这是我的问题。
Mesh<T>
必须实现transform
方法。 当然,每个顶点格式都有一个位置,如果它被一致地命名(.pos
),那么该方法就可以正常工作:
由于每个顶点肯定都有一个位置,如果我总是在Vertex*
结构中调用该成员.pos
,那么我可以添加一个方法.transform
到模板化类Mesh<T>
为:
void transform( Matrix4& mat )
{
each vertex
vertex.pos = mat * vertex.pos ; // transform it
}
现在搞砸了。如果顶点格式中有法线,那么该法线也必须被转换(通过"法线矩阵")。 现在我的模板中有一个"if 语句"? 我必须为每个顶点类型编写一个模板专用化.transform
方法(基本上分为 2 个类别,有法线的和没有法线的)?
我在这里滥用了模板吗? 错过了一些"编程船"?
这是我真正想在逻辑上做的事情:
void transform( Matrix4& mat )
{
each vertex in vertices
{
vertex.pos = mat * vertex.pos ; // transform it
#ifdef vertex.normal
vertex.normal = mat * vertex.normal ; // transform normal as well*
#endif
}
}
* 假设转换没有规模 (那么你不需要使用"普通矩阵")
如果你可以编写一个Vertex
层次结构,其中基只保存位置(和.pos
成员),一个VertexWNormal
保存.normal
,然后其余部分从那里继承,然后你可以添加非模板化的重载,让编译器处理:
void transform( Matrix4& m, VertexBase& v ) {
// transform only pos
}
void transform( Matrix4& m, VertexWNormal& v ) {
transform(m,static_cast<VertexBase&>(v));
// transform normal here
}
void tranform( Matrix4& m ) {
foreach vertex:
transform(m,vertex);
}
当然,这对你的设计是否有意义取决于你没有展示的很多东西,很难说。
如果你只有两种类型的顶点,我建议你按照大卫上面描述的去做: 只需创建两个执行转换的函数,并根据顶点类型使用重载调用它们。这也适用于更多的顶点类型,但每次添加新的顶点类型时,都需要再次重载函数。对于此处描述的简单函数来说,这可能是可以的,但如果函数实际上更复杂,则可能会变得烦人。
部分修复是创建一个特征类,该类告诉特定顶点类型是否具有普通成员。可以设置默认值,使其在大多数情况下是正确的,并且可以根据特征选择合适的函数。您仍将提供两个版本的代码,但对于每个额外的顶点类型,只需要定义特征:
template <typename> struct VertexHasNormal { enum { value = false }; };
template <> struct VertexHasNormal<VertexPNCT> { enum { value = true }; };
template <typename V, template T, template S>
void transform(V& vertices, Matrix4& m, T S::*member) {
for (auto& v: vertices) {
v.*member = mat * v.*member;
}
}
template <bool, typename T>
struct Transform {
template <typename V>
void call(V& vertices, Matrix4& m) {
transform(vertices, m, &T::pos);
}
};
template <typename T>
struct Transform<bool, T> {
template <typename V>
void call(V& vertices, Matrix4& m) {
transform(vertices, m, &T::pos);
transform(vertices, m, &T::normal);
}
};
template <typename T>
void Mash<T>::transform(Matrix4& m) {
Transform<VertexHasNormal::value>::call(this->vertices, m);
}
函数模板transform()
是对顶点序列执行实际转换的函数。我考虑它是因为我认为它可以被分解出来有点可爱,但它不需要被分解掉。它也不需要使用指向成员、auto
等的指针。Transform
类型只是使用的辅助类型,因为函数模板不能部分专用化。它专门研究顶点类型是否具有normal
成员的特征。最后,Mash<T>::transform()
只是调度到专用函数的相应版本。
唯一需要做的是在定义另一个具有normal
成员的顶点时添加新的特征专用化。这可能不可取。在这种情况下,可以使用类型特征确定结构是否具有名为normal
的可访问数据成员。但是,我认为我不能从头顶上输入这个。实现它的基本思想是利用替换失败不是错误的事实(" SFINAE),并且如果测试类型确实具有必要的成员,则可以设置某些内容以在两个潜在成员之间创建歧义。有一个 Boost 组件可以做到这一点,但如果您需要自己创建它,大约需要 10 行代码。
您可能希望创建结构类并从两个基类派生它们,一个具有普通基类,一个没有基类。然后,可以使用模板专用化在这两个基类(而不是所有顶点类)之间进行选择。
- 努力将整数转换为链表。不知道我在这里做错了什么
- 我可以在这里替换什么,因为我不能在 C# 中使用隐式变量的 lambda 函数?
- 当我从下面的代码中删除关键字 virtual 时,它可以正常工作,否则会出现错误。在这里"virtual"字的意义是什么?
- File.cpp.o:OpenPose 标志 CMakeFiles/.. 的多重定义/main.cpp.o:首先在这里定
- 为什么thread_local变量在这里从未初始化?
- 为什么我必须在这里使用dynamic_cast
- 在这里,当我们比较 if(vc[i]==vc1[i]) 时,它是向量数组. 实际上比较的值是多少,
- 我正在尝试使用 while 循环从字符串中删除字母,直到没有字母。我在这里做错了什么?
- 为什么 C++20 中的 [[可能]] 属性在这里引发警告?
- 我在这里正确传递参数了吗?
- 为什么gmp会在这里与"invalid next size"重新定位一起崩溃?
- 移动语义在这里如何工作?
- 如何在这里循环运行?
- 为什么枚举变量在这里是右值?
- 我的C++合并排序代码不起作用。我在这里错过了什么?
- 试图美化这个Arduino代码[初学者在这里]
- 复制交换习惯用法-我们可以在这里使用动态强制转换操作吗
- 在这里使用删除运算符是否正确,我很困惑
- 如何使用模板生成整数序列在这里工作
- 为什么sizeof函数在这里不能正常工作