C++ 泛型编程指南 非类型模板参数-4. 模板参数类型 auto

在 C++17 中,可以使用 auto 来定义非类型模板参数。这个特性允许我们创建一个确定大小的堆栈类,例如:

#include <array>
#include <cassert>

// 使用类型 T 和非类型模板参数 Maxsize 定义 Stack 类
template<typename T, auto Maxsize>
class Stack {
public:
    using size_type = decltype(Maxsize);  // Maxsize 的类型

private:
    std::array<T, Maxsize> elems;  // 存储元素的数组
    size_type numElems;            // 当前元素数量

public:
    Stack();                       // 构造函数
    void push(T const& elem);      // 压入元素
    void pop();                    // 弹出元素
    T const& top() const;          // 返回栈顶元素

    bool empty() const {           // 判断栈是否为空
        return numElems == 0;
    }

    size_type size() const {       // 返回栈的当前元素数量
        return numElems;
    }
};

// 构造函数实现
template<typename T, auto Maxsize>
Stack<T, Maxsize>::Stack()
: numElems(0) // 初始化元素数量为 0
{
    // 无需其他操作
}

// push 函数实现
template<typename T, auto Maxsize>
void Stack<T, Maxsize>::push(T const& elem)
{
    assert(numElems < Maxsize);   // 检查栈是否已满
    elems[numElems] = elem;        // 添加元素
    ++numElems;                    // 增加元素数量
}

// pop 函数实现
template<typename T, auto Maxsize>
void Stack<T, Maxsize>::pop()
{
    assert(!empty());             // 检查栈是否为空
    --numElems;                   // 减少元素数量
}

// top 函数实现
template<typename T, auto Maxsize>
T const& Stack<T, Maxsize>::top() const
{
    assert(!empty());             // 检查栈是否为空
    return elems[numElems - 1];   // 返回栈顶元素
}

通过使用 auto 作为占位符类型,Maxsize 可以是任何允许的非类型模板参数类型。内部实现可以同时使用这两个值:

std::array<T, Maxsize> elems;  // 元素存储
using size_type = decltype(Maxsize);  // Maxsize 的类型定义

这个设计允许 Stack 类灵活地处理不同类型和大小的堆栈。

以下是关于使用 auto 确定返回类型的内容重新排版,其中展示了如何在 C++14 后使用 auto 作为返回类型,以及如何基于模板参数类型进行不同的操作:


在 C++14 之后,可以在类的方法中使用 auto 作为返回类型,编译器将自动确定返回类型。以下是一个例子:

// 使用 auto 作为 size() 的返回类型
auto size() const {
    return numElems; // 返回当前元素数量
}

通过这个类的声明,当我们在使用堆栈时,元素数量的类型取决于模板参数的类型定义。例如:

// 文件: basics/stackauto.cpp

#include <iostream>
#include <string>
#include "stackauto.hpp"

int main() {
    Stack<int, 20u> int20Stack;  // 最多可容纳 20 个 int 的栈
    Stack<std::string, 40> stringStack;  // 最多可容纳 40 个字符串的栈

    // 操作 int 类型栈(最多 20 个元素)
    int20Stack.push(7);
    std::cout << int20Stack.top() << '\n';
    auto size1 = int20Stack.size();

    // 操作 string 类型栈(最多 40 个元素)
    stringStack.push("hello");
    std::cout << stringStack.top() << '\n';
    auto size2 = stringStack.size();

    // 检查 int20Stack 和 stringStack 的 size() 返回类型是否相同
    if (!std::is_same_v<decltype(size1), decltype(size2)>) {
        std::cout << "size types differ" << '\n';
    }
}

在这个例子中:

Stack<int, 20u> int20Stack;  // 最多可容纳 20 个 int 的栈

由于传递了 20u,因此内部的大小类型为 unsigned int

Stack<std::string, 40> stringStack;  // 最多可容纳 40 个字符串的栈

由于传递了 40,因此内部的大小类型为 int

因此,两个堆栈的 size() 返回的类型不同:

auto size1 = int20Stack.size();
auto size2 = stringStack.size();

size1size2 的类型会有所不同。我们可以使用标准类型特征 std::is_samedecltype 进行检查:

if (!std::is_same_v<decltype(size1), decltype(size2)>) {
    std::cout << "size types differ" << '\n';
}

因此,程序的输出将是:

size types differ

通过这种方式,我们可以灵活地在模板中处理不同类型参数和非类型参数组合。

以下是关于 C++17 后的一些新特性重新排版的内容,展示了如何简化类型特征的使用和更灵活的模板参数使用:


在 C++17 之后,可以使用 _v 后缀来代替使用 ::value,这使代码更简洁。以下是一个示例:

// 比较 size1 和 size2 的类型是否相同
if (!std::is_same_v<decltype(size1), decltype(size2)>) {
    std::cout << "size types differ" << '\n';
}

对非类型模板参数的限制仍然适用。尤其是,非类型模板参数不能是浮点数类型。例如:

Stack<int, 3.14> sd;  // 错误:浮点数类型的非类型参数

在 C++17 中,可以通过 auto 接受任意类型的非类型参数,还可以传递字符串作为常量数组(甚至可以是静态的局部声明)。例如:

// 文件: basics/message.cpp

#include <iostream>

// 接受任意可能的非类型参数值(自 C++17 开始)
template<auto T>
class Message {
public:
    void print() {
        std::cout << T << '\n';
    }
};

int main() {
    Message<42> msg1;
    msg1.print();  // 使用 int 42 进行初始化并打印此值

    static char const s[] = "hello";
    Message<s> msg2;  // 使用 char const[6] "hello" 进行初始化
    msg2.print();     // 打印该值
}

此外,使用 template<decltype(auto) N> 可以支持更复杂的类型推导,这也允许将模板参数实例化为引用。例如:

template<decltype(auto) N>
class C {
    // ...
};

int i;
C<(i)> x;  // N 是 int&

关于更多细节可以参考第 15.10.1 节。这种方式允许模板参数拥有更多的灵活性和应用场景。

上一篇:【TypeScript】知识点梳理(三)


下一篇:Stable Diffusion绘画 | 如何做到不同动作表情,人物角色保持一致性(上篇)