掌握C++模板的艺术:类型参数、默认值和自动推导

类型模板参数在 Grid 示例中,Grid 模板有一个模板参数:存储在网格中的类型 。编写类模板时,您需要在尖括号内指定参数列表 , 例如:
template <typename T>这个参数列表类似于函数或方法中的参数列表 。与函数和方法一样,你可以编写具有任意多个模板参数的类 。此外 , 这些参数不必是类型,它们可以有默认值 。
非类型模板参数非类型参数是普通参数,如整数和指针——这类参数你可能已经在函数和方法中很熟悉了 。然而 , 非类型模板参数只能是整型(charintlong 等)、枚举类型、指针、引用、std::nullptr_tautoauto& 和 auto* 。C++20 还允许浮点类型和类类型的非类型模板参数 。后者有很多限制,在本文中不再详细讨论 。
在 Grid 类模板中,你可以使用非类型模板参数来指定网格的高度和宽度 , 而不是在构造函数中指定 。在模板列表中指定非类型参数而不是在构造函数中指定的主要优点是这些值在代码编译之前就已知 。回想一下 , 编译器通过在编译之前替换模板参数来生成模板实例的代码 。因此,你可以在实现中使用普通的二维数组,而不是动态调整大小的向量数组 。以下是带有更改的新类定义:
export template <typename T, size_t WIDTH, size_t HEIGHT>class Grid {public:    Grid() = default;    virtual ~Grid() = default;    // 明确默认复制构造函数和赋值运算符 。    Grid(const Grid& src) = default;    Grid& operator=(const Grid& rhs) = default;    std::optional<T>& at(size_t x, size_t y);    const std::optional<T>& at(size_t x, size_t y) const;    size_t getHeight() const { return HEIGHT; }    size_t getWidth() const { return WIDTH; }private:    void verifyCoordinate(size_t x, size_t y) const;    std::optional<T> m_cells[WIDTH][HEIGHT];};注意,模板参数列表需要三个参数:存储在网格中的对象类型,以及网格的宽度和高度 。宽度和高度用于创建存储对象的二维数组 。下面是类方法的定义:
// 类方法定义template <typename T, size_t WIDTH, size_t HEIGHT>void Grid<T, WIDTH, HEIGHT>::verifyCoordinate(size_t x, size_t y) const {    if (x >= WIDTH) {        throw std::out_of_range { std::format("{} must be less than {}.", x, WIDTH) };    }    if (y >= HEIGHT) {        throw std::out_of_range { std::format("{} must be less than {}.", y, HEIGHT) };    }}template <typename T, size_t WIDTH, size_t HEIGHT>const std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(size_t x, size_t y) const {    verifyCoordinate(x, y);    return m_cells[x][y];}template <typename T, size_t WIDTH, size_t HEIGHT>std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(size_t x, size_t y) {    return const_cast<std::optional<T>&>(std::as_const(*this).at(x, y));}注意,之前你在哪里指定了 Grid<T>,现在你必须指定 Grid<T, WIDTH, HEIGHT> 来指定三个模板参数 。你可以这样实例化并使用这个模板:
Grid<int, 10, 10> myGrid;Grid<int, 10, 10> anotherGrid;myGrid.at(2, 3) = 42;anotherGrid = myGrid;cout << anotherGrid.at(2, 3).value_or(0);这段代码看起来很棒,但不幸的是 , 存在比你最初预期的更多限制 。首先,你不能使用非常量整数来指定高度或宽度 。以下代码无法编译:
size_t height { 10 };Grid<int, 10, height> testGrid; // 无法编译


推荐阅读