在 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();
size1
和 size2
的类型会有所不同。我们可以使用标准类型特征 std::is_same
和 decltype
进行检查:
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 节。这种方式允许模板参数拥有更多的灵活性和应用场景。