类模板的非类型参数
简单来说,模板参数,并不一定是类型参数(如 typename T),也可以是非类型参数(如int等)
比如,可以设计方便用户自定义容量的stack:
#include <array>
#include <cassert>
#include <iostream>
// Maxsize 指定了Stack的最大容量
template<typename T, std::size_t MaxSize>
class Stack {
private:
std::array<T,MaxSize> elems; // 固定容量的数据,来限制栈的大小
std::size_t cur_size{0}; //当前栈的大小, 直接初始化为0
public:
Stack& operator=(const Stack&);
void push(const T& elem);
void pop();
const T& top() const;
bool empty() const {
return cur_size == 0;
}
};
template<typename T, std::size_t MaxSize>
void Stack<T,MaxSize>::push(const T& elem) {
assert(cur_size < MaxSize);
elems[cur_size++] = elem;
}
template<typename T, std::size_t MaxSize>
void Stack<T, MaxSize>::pop() {
assert(!empty());
cur_size--;
}
template<typename T, std::size_t MaxSize>
const T& Stack<T, MaxSize>::top() const {
assert(!empty());
return elems[cur_size-1];
}
int main() {
Stack<int, 20> int20stack; // 申明一个容量大小为20, 元素类型为int的stack
Stack<int, 40> int40stack; // 申明一个容量大小为40, 元素类型为int的stack
Stack<std::string,40> stringStack; // 申明一个容量大小为40, 元素类型为string的stack
int20stack.push(7);
std::cout << int20stack.top() << '\n';
int20stack.pop();
stringStack.push("hello");
std::cout << stringStack.top() << '\n';
stringStack.pop();
return 0;
}
函数模板的非类型参数
同样,函数模板也可以使用非类型参数
比如,想要给vector中的每个元素都加上一个固定值,则可以:
#include <vector>
#include <algorithm>
#include <iostream>
template<int Val, typename T>
T addValue(T x) {
return x + Val;
}
int main() {
std::vector<int> arr{1, 2, 3, 4, 5};
// 遍历容器内的每个元素,然后全加2
std::transform(arr.begin(), arr.end(), arr.begin(), addValue<2,int>);
for(auto it : arr) {
std::cout << it << std::endl;
}
}
或者使用C++的auto 模板参数推导:
// C++ 17 新特性, 可以使用auto 来描述模板参数
template<auto Val, typename T = decltype(Val)>
T addValue(T x) {
return x + Val;
}
int main() {
std::vector<int> arr{1, 2, 3, 4, 5};
// 遍历容器内的每个元素,然后全加2
std::transform(arr.begin(), arr.end(), arr.begin(), addValue<3>);
for(auto it : arr) {
std::cout << it << std::endl;
}
}
// 或者确保传递的值,与传递的类型相同
template<typename T, T Val = T{}>
T addValue(T x) {
return x + Val;
}
int main() {
std::vector<int> arr{1, 2, 3, 4, 5};
// 遍历容器内的每个元素,然后全加2
std::transform(arr.begin(), arr.end(), arr.begin(), addValue<int,6>);
for(auto it : arr) {
std::cout << it << std::endl;
}
}
非模板参数限制
- 非类型模板参数有一些限制,只能是整型常量值 (包括枚举),指向对象/函数/成员的指针,指 向对象或函数的左值引用,或者 std::nullptr_t(nullptr 的类型)
- 浮点数和类型对象不能作为非模板参数
template<double VAT> // 错误,不允许浮点作为非模板参数
double process (double v) {
return v * VAT;
}
template<std::string name> // 错误,不允许类型对象作为非模板参数
class MyClass {
...
};
模板参数类型 auto
C++17 后,可以使用auto定义非类型模板参数,稍微改造一下之前的Stack代码即可:
// 其余和原来一样
template<typename T, auto Maxsize> class Stack {
public:
using size_type = decltype(Maxsize); // 拿到真正的类型,方便后续的定义
};
当然,auto 关键字也无法突破 非类型模板参数本身的限制,比如如果auto推导成double,同样也会编译失败:
// 支持任意合法的非类型模板参数
template <auto Value>
void Print() {
std::cout<<Value<<std::endl;
}
int main() {
Print<1>(); // OK, Value 被推导为int
// Print<"true">(); // No, Value 不能为字符串的字面量
// Print<2.3>(); // No, Value 不能为浮点数
static char const str[] = "hello"; // yes, 可以将静态字符数组传入非类型模板参数中
Print<str>();
}
总结
• 模板的模板参数可以是值,而非类型。
• 不能将浮点数或类类型对象作为非类型模板的参数。对于指向字符串字面量、临时对象和子
对象的指针/引用,有一些限制。
• 使用 auto 可使模板具有泛型值的非类型模板参数。