如何在专门的模板结构中有效地重用代码?

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 维向量(通常是 vec2vec3vec4)。因此,一个有用的特性是能够在可能的情况下访问向量中的 x、y、z 和 w 值。但是,这样做有一些问题。

xyzw 成员需要成为 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 子向量结构的值而不是父结构的值的方式执行此操作。

所以,我的问题是:我如何有效地重用专用模板结构中的代码,同时仍然能够拥有(在这种情况下)xyz, 和 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>
 { };

以下是一个完整的编译示例,显示 wmyVector<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) 成员引用,其名称来自 xyz , 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>>
{
    // ....
};