如何在专门的模板结构中有效地重用代码?
How would one efficiently reuse code in a specialised template struct?
我正在为数学库创建自己的向量结构。
目前,我会像这样创建结构:
template <unsigned int size, typename T>
struct vector {
// array of elements
T elements[size];
// ...
};
然而,数学库的主要用例将导致主要使用 2 维、3 维和 4 维向量(通常是 vec2
、vec3
和vec4
)。因此,一个有用的特性是能够在可能的情况下访问向量中的 x、y、z 和 w 值。但是,这样做有一些问题。
x
、y
、z
和 w
成员需要成为 elements[0]
、elements[1]
的引用变量,等。这意味着,如果向量少于 4 个元素,则不会初始化某些引用。
当然,这可以通过专业模板来实现,这就是我目前正在做的:
template <unsigned int size, typename T>
struct vector {
// ...
}
template <typename T>
struct vector<2, T> {
// same as with before, except with references to X and Y elements.
// these are successfully initialised in the constructor because the vector is guaranteed to have 2 elements
T &x;
T &y;
// ...
}
// and so on for 3D and 4D vectors
这个有效,但远非方便。实际上,vector
结构很大并且有很多函数和运算符重载。当它专门化为其他尺寸时,这些函数和运算符重载需要从通用结构复制粘贴到 2D、3D 和 4D 结构,这是非常低效的。请记住:我在专业化之间唯一改变的是参考变量!所有其他成员都是完全相同,所以我'我宁愿重用他们的代码。
另一种解决方案是从一个基础继承class。我不完全确定如何以允许继承的运算符重载到 return 子向量结构的值而不是父结构的值的方式执行此操作。
所以,我的问题是:我如何有效地重用专用模板结构中的代码,同时仍然能够拥有(在这种情况下)x
、y
、z
, 和 w
参考资料,何时可用?
如果您愿意稍微更改接口,通过函数访问成员,即
vector<2, int> v;
v.x() = 5; // instead of v.x = 5;
然后你可以在没有任何专业化的情况下做到这一点,并完全回避代码重用的问题。
在 class 模板中,为每个索引添加尽可能多的成员函数,并断言访问有效:
template <unsigned int size, typename T>
struct vector {
T elements[size];
// ...
T& x() {
static_assert(size > 0);
return elements[0];
}
T& y() {
static_assert(size > 1);
return elements[1];
}
// ... and so on
};
现在这将在访问适当的元素时起作用,否则会报错。
vector<1, int> v1;
vector<2, int> v2;
v1.x() = 5; // ok
v1.y() = 4; // error, v1 can only access x
v2.y() = 3; // ok, v2 is big enough
这是一个demo。
您可以为成员函数
编写一个requires
约束,而不是static_assert
T& x() requires (size > 0) {
return elements[0];
}
// etc ...
这里是 demo。
不确定你到底想要什么,但是......只是为了好玩......你可以编写一个自递归基础class如下
// generic case: starting recursion point for size > 3
template <std::size_t size, typename T, std::size_t sizeArr>
struct myVectorBase : public myVectorBase<3u, T, sizeArr>
{ T & w = myVectorBase<3u, T, sizeArr>::elements[3u]; };
// recursion ground case: elements definition
template <typename T, std::size_t sizeArr>
struct myVectorBase<0u, T, sizeArr>
{ T elements[sizeArr]; };
// special case for myVector<0, T>: an array of size zero isn't standard
template <typename T>
struct myVectorBase<0u, T, 0u>
{ };
template <typename T, std::size_t sizeArr>
struct myVectorBase<1u, T, sizeArr> : public myVectorBase<0u, T, sizeArr>
{ T & x = myVectorBase<0u, T, sizeArr>::elements[0u]; };
template <typename T, std::size_t sizeArr>
struct myVectorBase<2u, T, sizeArr> : public myVectorBase<1u, T, sizeArr>
{ T & y = myVectorBase<1u, T, sizeArr>::elements[1u]; };
template <typename T, std::size_t sizeArr>
struct myVectorBase<3u, T, sizeArr> : public myVectorBase<2u, T, sizeArr>
{ T & z = myVectorBase<2u, T, sizeArr>::elements[2u]; };
定义一个elements[sizeArr]
数组(继承自myVectorBase<0u, T, sizeArr>
),一个元素x
(继承自myVectorBase<1u, T, sizeArr>
,当起始size
大于大于零),一个元素 y
(继承自 myVectorBase<2u, T, sizeArr>
,当起始 size
大于 1 时),一个元素 z
(继承自 myVectorBase<3u, T, sizeArr>
,当起始 size
大于 2) 和元素 w
(继承自 myVectorBase<4u, T, sizeArr>
,当起始 size
大于 3)
现在您可以定义 myVector
(请避免使用标准库中使用的 vector
名称)如下
template <std::size_t size, typename T>
struct myVector : myVectorBase<size, T, size>
{ };
以下是一个完整的编译示例,显示 w
在 myVector<5u, int>
中可用但在 myVector<2u, int>
中不可用(其中 y
可用)
#include <iostream>
template <std::size_t size, typename T, std::size_t sizeArr>
struct myVectorBase : public myVectorBase<3u, T, sizeArr>
{ T & w = myVectorBase<3u, T, sizeArr>::elements[3u]; };
template <typename T, std::size_t sizeArr>
struct myVectorBase<0u, T, sizeArr>
{ T elements[sizeArr]; };
template <typename T>
struct myVectorBase<0u, T, 0u>
{ };
template <typename T, std::size_t sizeArr>
struct myVectorBase<1u, T, sizeArr> : public myVectorBase<0u, T, sizeArr>
{ T & x = myVectorBase<0u, T, sizeArr>::elements[0u]; };
template <typename T, std::size_t sizeArr>
struct myVectorBase<2u, T, sizeArr> : public myVectorBase<1u, T, sizeArr>
{ T & y = myVectorBase<1u, T, sizeArr>::elements[1u]; };
template <typename T, std::size_t sizeArr>
struct myVectorBase<3u, T, sizeArr> : public myVectorBase<2u, T, sizeArr>
{ T & z = myVectorBase<2u, T, sizeArr>::elements[2u]; };
template <std::size_t size, typename T>
struct myVector : myVectorBase<size, T, size>
{ };
int main ()
{
myVector<5u, int> m5i;
m5i.w = 42; // size is 5 -> w is available
std::cout << m5i.elements[3u] << '\n'; // print 42
myVector<2u, int> m2i;
// m2i.w = 37; // compilation error: size is 2 -> w is unavailable
// m2i.z = 37; // compilation error: size is 2 -> z is unavailable
m2i.y = 37; // size is 2 -> y is unavailable
std::cout << m2i.elements[1u] << '\n'; // print 37
myVector<0u, int> m0i; // compile but is empty
}
正如另一个答案的评论中正确指出的那样,拥有引用字段是一件很痛苦的事情,因为您无法重新分配引用,因此 operator=
不会自动生成。而且,您不能真正自己实施它。此外,在典型的实现中,引用字段仍然占用一些内存,即使它指向结构内部。
然而,为了完整性,这是我的答案:在 C++ 元编程中,如果您需要动态地将 add/remove 字段转换为 class,您可以使用继承。您还可以使用 Curiously Recurring Template Pattern (CRTP) 从基础访问派生结构。
一种可能的实现如下。 vector_member_aliases<size, T, Derived>
是 class Derived
的基础,它准确地提供 min(0, size)
成员引用,其名称来自 x
、y
、z
, w
.我还在它们之间使用继承来避免代码重复。
#include <iostream>
template <unsigned int size, typename T, typename Derived>
struct vector_member_aliases : vector_member_aliases<3, T, Derived> {
T &w = static_cast<Derived*>(this)->elements[3];
};
template <typename T, typename Derived>
struct vector_member_aliases<0, T, Derived> {};
template <typename T, typename Derived>
struct vector_member_aliases<1, T, Derived> : vector_member_aliases<0, T, Derived> {
T &x = static_cast<Derived*>(this)->elements[0];
};
template <typename T, typename Derived>
struct vector_member_aliases<2, T, Derived> : vector_member_aliases<1, T, Derived> {
T &y = static_cast<Derived*>(this)->elements[1];
};
template <typename T, typename Derived>
struct vector_member_aliases<3, T, Derived> : vector_member_aliases<2, T, Derived> {
T &z = static_cast<Derived*>(this)->elements[2];
};
template <unsigned int size, typename T>
struct vector : vector_member_aliases<size, T, vector<size, T>> {
// array of elements
T elements[size]{};
void print_all() {
for (unsigned int i = 0; i < size; i++) {
if (i > 0) {
std::cout << " ";
}
std::cout << elements[i];
}
std::cout << "\n";
}
};
int main() {
[[maybe_unused]] vector<0, int> v0;
// v0.x = 10;
vector<1, int> v1;
v1.x = 10;
// v1.y = 20;
v1.print_all();
vector<2, int> v2;
v2.x = 11;
v2.y = 21;
// v2.z = 31;
v2.print_all();
vector<3, int> v3;
v3.x = 12;
v3.y = 22;
v3.z = 32;
// v3.w = 42;
v3.print_all();
vector<4, int> v4;
v4.x = 13;
v4.y = 23;
v4.z = 33;
v4.w = 43;
v4.print_all();
std::cout << sizeof(v4) << "\n";
}
另一种实现是创建四个独立的classes并使用std::condition_t
来选择继承哪些,用empty_base
替换哪些(每个跳过的变量不同) :
#include <iostream>
#include <type_traits>
template<int>
struct empty_base {};
template <typename T, typename Derived>
struct vector_member_alias_x {
T &x = static_cast<Derived*>(this)->elements[0];
};
// Skipped: same struct for for y, z, w
template <unsigned int size, typename T>
struct vector
: std::conditional_t<size >= 1, vector_member_alias_x<T, vector<size, T>>, empty_base<0>>
, std::conditional_t<size >= 2, vector_member_alias_y<T, vector<size, T>>, empty_base<1>>
, std::conditional_t<size >= 3, vector_member_alias_z<T, vector<size, T>>, empty_base<2>>
, std::conditional_t<size >= 4, vector_member_alias_w<T, vector<size, T>>, empty_base<3>>
{
// ....
};
我正在为数学库创建自己的向量结构。
目前,我会像这样创建结构:
template <unsigned int size, typename T>
struct vector {
// array of elements
T elements[size];
// ...
};
然而,数学库的主要用例将导致主要使用 2 维、3 维和 4 维向量(通常是 vec2
、vec3
和vec4
)。因此,一个有用的特性是能够在可能的情况下访问向量中的 x、y、z 和 w 值。但是,这样做有一些问题。
x
、y
、z
和 w
成员需要成为 elements[0]
、elements[1]
的引用变量,等。这意味着,如果向量少于 4 个元素,则不会初始化某些引用。
当然,这可以通过专业模板来实现,这就是我目前正在做的:
template <unsigned int size, typename T>
struct vector {
// ...
}
template <typename T>
struct vector<2, T> {
// same as with before, except with references to X and Y elements.
// these are successfully initialised in the constructor because the vector is guaranteed to have 2 elements
T &x;
T &y;
// ...
}
// and so on for 3D and 4D vectors
这个有效,但远非方便。实际上,vector
结构很大并且有很多函数和运算符重载。当它专门化为其他尺寸时,这些函数和运算符重载需要从通用结构复制粘贴到 2D、3D 和 4D 结构,这是非常低效的。请记住:我在专业化之间唯一改变的是参考变量!所有其他成员都是完全相同,所以我'我宁愿重用他们的代码。
另一种解决方案是从一个基础继承class。我不完全确定如何以允许继承的运算符重载到 return 子向量结构的值而不是父结构的值的方式执行此操作。
所以,我的问题是:我如何有效地重用专用模板结构中的代码,同时仍然能够拥有(在这种情况下)x
、y
、z
, 和 w
参考资料,何时可用?
如果您愿意稍微更改接口,通过函数访问成员,即
vector<2, int> v;
v.x() = 5; // instead of v.x = 5;
然后你可以在没有任何专业化的情况下做到这一点,并完全回避代码重用的问题。
在 class 模板中,为每个索引添加尽可能多的成员函数,并断言访问有效:
template <unsigned int size, typename T>
struct vector {
T elements[size];
// ...
T& x() {
static_assert(size > 0);
return elements[0];
}
T& y() {
static_assert(size > 1);
return elements[1];
}
// ... and so on
};
现在这将在访问适当的元素时起作用,否则会报错。
vector<1, int> v1;
vector<2, int> v2;
v1.x() = 5; // ok
v1.y() = 4; // error, v1 can only access x
v2.y() = 3; // ok, v2 is big enough
这是一个demo。
您可以为成员函数
编写一个requires
约束,而不是static_assert
T& x() requires (size > 0) {
return elements[0];
}
// etc ...
这里是 demo。
不确定你到底想要什么,但是......只是为了好玩......你可以编写一个自递归基础class如下
// generic case: starting recursion point for size > 3
template <std::size_t size, typename T, std::size_t sizeArr>
struct myVectorBase : public myVectorBase<3u, T, sizeArr>
{ T & w = myVectorBase<3u, T, sizeArr>::elements[3u]; };
// recursion ground case: elements definition
template <typename T, std::size_t sizeArr>
struct myVectorBase<0u, T, sizeArr>
{ T elements[sizeArr]; };
// special case for myVector<0, T>: an array of size zero isn't standard
template <typename T>
struct myVectorBase<0u, T, 0u>
{ };
template <typename T, std::size_t sizeArr>
struct myVectorBase<1u, T, sizeArr> : public myVectorBase<0u, T, sizeArr>
{ T & x = myVectorBase<0u, T, sizeArr>::elements[0u]; };
template <typename T, std::size_t sizeArr>
struct myVectorBase<2u, T, sizeArr> : public myVectorBase<1u, T, sizeArr>
{ T & y = myVectorBase<1u, T, sizeArr>::elements[1u]; };
template <typename T, std::size_t sizeArr>
struct myVectorBase<3u, T, sizeArr> : public myVectorBase<2u, T, sizeArr>
{ T & z = myVectorBase<2u, T, sizeArr>::elements[2u]; };
定义一个elements[sizeArr]
数组(继承自myVectorBase<0u, T, sizeArr>
),一个元素x
(继承自myVectorBase<1u, T, sizeArr>
,当起始size
大于大于零),一个元素 y
(继承自 myVectorBase<2u, T, sizeArr>
,当起始 size
大于 1 时),一个元素 z
(继承自 myVectorBase<3u, T, sizeArr>
,当起始 size
大于 2) 和元素 w
(继承自 myVectorBase<4u, T, sizeArr>
,当起始 size
大于 3)
现在您可以定义 myVector
(请避免使用标准库中使用的 vector
名称)如下
template <std::size_t size, typename T>
struct myVector : myVectorBase<size, T, size>
{ };
以下是一个完整的编译示例,显示 w
在 myVector<5u, int>
中可用但在 myVector<2u, int>
中不可用(其中 y
可用)
#include <iostream>
template <std::size_t size, typename T, std::size_t sizeArr>
struct myVectorBase : public myVectorBase<3u, T, sizeArr>
{ T & w = myVectorBase<3u, T, sizeArr>::elements[3u]; };
template <typename T, std::size_t sizeArr>
struct myVectorBase<0u, T, sizeArr>
{ T elements[sizeArr]; };
template <typename T>
struct myVectorBase<0u, T, 0u>
{ };
template <typename T, std::size_t sizeArr>
struct myVectorBase<1u, T, sizeArr> : public myVectorBase<0u, T, sizeArr>
{ T & x = myVectorBase<0u, T, sizeArr>::elements[0u]; };
template <typename T, std::size_t sizeArr>
struct myVectorBase<2u, T, sizeArr> : public myVectorBase<1u, T, sizeArr>
{ T & y = myVectorBase<1u, T, sizeArr>::elements[1u]; };
template <typename T, std::size_t sizeArr>
struct myVectorBase<3u, T, sizeArr> : public myVectorBase<2u, T, sizeArr>
{ T & z = myVectorBase<2u, T, sizeArr>::elements[2u]; };
template <std::size_t size, typename T>
struct myVector : myVectorBase<size, T, size>
{ };
int main ()
{
myVector<5u, int> m5i;
m5i.w = 42; // size is 5 -> w is available
std::cout << m5i.elements[3u] << '\n'; // print 42
myVector<2u, int> m2i;
// m2i.w = 37; // compilation error: size is 2 -> w is unavailable
// m2i.z = 37; // compilation error: size is 2 -> z is unavailable
m2i.y = 37; // size is 2 -> y is unavailable
std::cout << m2i.elements[1u] << '\n'; // print 37
myVector<0u, int> m0i; // compile but is empty
}
正如另一个答案的评论中正确指出的那样,拥有引用字段是一件很痛苦的事情,因为您无法重新分配引用,因此 operator=
不会自动生成。而且,您不能真正自己实施它。此外,在典型的实现中,引用字段仍然占用一些内存,即使它指向结构内部。
然而,为了完整性,这是我的答案:在 C++ 元编程中,如果您需要动态地将 add/remove 字段转换为 class,您可以使用继承。您还可以使用 Curiously Recurring Template Pattern (CRTP) 从基础访问派生结构。
一种可能的实现如下。 vector_member_aliases<size, T, Derived>
是 class Derived
的基础,它准确地提供 min(0, size)
成员引用,其名称来自 x
、y
、z
, w
.我还在它们之间使用继承来避免代码重复。
#include <iostream>
template <unsigned int size, typename T, typename Derived>
struct vector_member_aliases : vector_member_aliases<3, T, Derived> {
T &w = static_cast<Derived*>(this)->elements[3];
};
template <typename T, typename Derived>
struct vector_member_aliases<0, T, Derived> {};
template <typename T, typename Derived>
struct vector_member_aliases<1, T, Derived> : vector_member_aliases<0, T, Derived> {
T &x = static_cast<Derived*>(this)->elements[0];
};
template <typename T, typename Derived>
struct vector_member_aliases<2, T, Derived> : vector_member_aliases<1, T, Derived> {
T &y = static_cast<Derived*>(this)->elements[1];
};
template <typename T, typename Derived>
struct vector_member_aliases<3, T, Derived> : vector_member_aliases<2, T, Derived> {
T &z = static_cast<Derived*>(this)->elements[2];
};
template <unsigned int size, typename T>
struct vector : vector_member_aliases<size, T, vector<size, T>> {
// array of elements
T elements[size]{};
void print_all() {
for (unsigned int i = 0; i < size; i++) {
if (i > 0) {
std::cout << " ";
}
std::cout << elements[i];
}
std::cout << "\n";
}
};
int main() {
[[maybe_unused]] vector<0, int> v0;
// v0.x = 10;
vector<1, int> v1;
v1.x = 10;
// v1.y = 20;
v1.print_all();
vector<2, int> v2;
v2.x = 11;
v2.y = 21;
// v2.z = 31;
v2.print_all();
vector<3, int> v3;
v3.x = 12;
v3.y = 22;
v3.z = 32;
// v3.w = 42;
v3.print_all();
vector<4, int> v4;
v4.x = 13;
v4.y = 23;
v4.z = 33;
v4.w = 43;
v4.print_all();
std::cout << sizeof(v4) << "\n";
}
另一种实现是创建四个独立的classes并使用std::condition_t
来选择继承哪些,用empty_base
替换哪些(每个跳过的变量不同) :
#include <iostream>
#include <type_traits>
template<int>
struct empty_base {};
template <typename T, typename Derived>
struct vector_member_alias_x {
T &x = static_cast<Derived*>(this)->elements[0];
};
// Skipped: same struct for for y, z, w
template <unsigned int size, typename T>
struct vector
: std::conditional_t<size >= 1, vector_member_alias_x<T, vector<size, T>>, empty_base<0>>
, std::conditional_t<size >= 2, vector_member_alias_y<T, vector<size, T>>, empty_base<1>>
, std::conditional_t<size >= 3, vector_member_alias_z<T, vector<size, T>>, empty_base<2>>
, std::conditional_t<size >= 4, vector_member_alias_w<T, vector<size, T>>, empty_base<3>>
{
// ....
};