我正在為數學庫創建自己的向量結構。
目前,我會像這樣創建結構:
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結構體中,這是非常低效的。請記住:我在專業化之間唯一改變的是參考變數!所有其他成員都完全相同,所以我寧愿重用他們的代碼。
另一種解決方案是從一個基類繼承。我不完全確定如何以允許繼承的運算子多載回傳子向量結構的值而不是父結構的值的方式執行此操作。
所以,我的問題是:我將如何有效地重復使用,同時仍能夠具有(在這種情況下)在一個專門的模板結構的代碼x,y,z,和w參考,當可用?
uj5u.com熱心網友回復:
如果您愿意通過函式訪問成員來稍微更改界面,即
vector<2, int> v;
v.x() = 5; // instead of v.x = 5;
那么你可以在沒有任何專業化的情況下做到這一點,并完全回避代碼重用的問題。
在類模板中,只需為您可能需要的每個索引添加盡可能多的成員函式,并斷言訪問有效:
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
這是一個演示。
代替static_assert,您可以requires為成員函式撰寫約束
T& x() requires (size > 0) {
return elements[0];
}
// etc ...
這是一個演示。
uj5u.com熱心網友回復:
正如另一個答案的評論中正確指出的那樣,擁有參考欄位是一個很大的痛苦,因為您無法重新分配參考,因此operator=不會自動生成。此外,您無法真正自己實作它。此外,在典型的實作中,即使參考欄位指向結構內部,它仍會占用一些記憶體。
但是,為了完整起見,這是我的答案:在 C 元編程中,如果您需要在類中動態添加/洗掉欄位,則可以使用繼承。您還可以使用Curiously Recurring Template Pattern (CRTP)從基礎訪問派生結構。
一種可能的實作如下。vector_member_aliases<size, T, Derived>是一個類的基礎,該類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";
}
另一種實作是創建四個獨立的類,并使用它們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>>
{
// ....
};
uj5u.com熱心網友回復:
不確定你到底想要什么,但是......只是為了好玩......你可以寫一個自遞回基類如下
// 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大于 0 時)比 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
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/401078.html
