非类型模板参数 Nontype Template Parameters
除了类型可以作为模板参数,普通值也可以作为模板函数,即非类型模板参数(Nontype Template Parameters)。
非类型类模板参数
前一章使用的例子 Stack 使用的是标准库中的容器管理元素,也可以使用固定大小的 std::array ,它的优势是内存管理开销更小,数组的大小可以交给用户指定。
#include <array>
#include <cassert>
template<typename T, std::size_t Maxsize>
class Stack {
private:
std::array<T,Maxsize> elems;
std::size_t numElems;
public:
Stack();
void push(T const& elem);
void pop();
T const& top() const;
bool empty() const {
return numElems == 0;
}
std::size_t size() const {
return numElems;
}
};
template<typename T, std::size_t Maxsize>
Stack<T,Maxsize>::Stack ()
: numElems(0)
{
}
template<typename T, std::size_t Maxsize>
void Stack<T,Maxsize>::push (T const& elem)
{
assert(numElems < Maxsize);
elems[numElems] = elem;
++numElems;
}
template<typename T, std::size_t Maxsize>
void Stack<T,Maxsize>::pop ()
{
assert(!elems.empty());
--numElems;
}
template<typename T, std::size_t Maxsize>
T const& Stack<T,Maxsize>::top () const
{
assert(!elems.empty());
return elems[numElems-1];
}
int main()
{
Stack<int,20> int20Stack;
Stack<int,40> int40Stack;
Stack<std::string,40> stringStack;
int20Stack.push(7);
std::cout << int20Stack.top() << '\n';
int20Stack.pop();
stringStack.push("hello");
std::cout << stringStack.top() << '\n';
stringStack.pop();
}
使用该模板需要同时指定类型和个数。 Maxsize 用于指定 std::array 的大小。非类型模板参数也可以有默认值。
template<typename T = int, std::size_t Maxsize = 100>
class Stack {
...
};
非类型函数模板参数
也可以为函数定义非类型模板参数。
template<int Val, typename T>
T addValue (T x)
{
return x + Val;
}
std::transform (source.begin(), source.end(),
dest.begin(),
addValue<5,int>);
也可以指定一个模板参数,由该参数之前的参数推断出其类型。
template<auto Val, typename T = decltype(Val)>
T foo();
或者保证传值的类型和指定的类型相同。
template<typename T, T Val = T{}>
T bar();
非类型模板参数的限制
需要注意的是,非类型模板参数有一定的限制。一般地,非类型模板参数可以是整形(包括枚举)、指向对象/函数/成员的指针、对象/函数的左值引用或空指针类型 std::nullptr_t 。
浮点数和类类型对象不可以作为非类型模板参数。
template<double VAT>
double process (double v)
{
return v * VAT;
}
template<std::string name>
class MyClass {
...
};
当使用指针或引用作为非类型模板参数时,不能用字符串字面值、临时对象、数据成员或其他子对象作模板实参。
template<char const* name>
class MyClass {
...
};
MyClass<"hello"> x;
C++版本逐渐放宽了限制。C+11 之前,对象必须有外部链接;C++17 之前对象必须有外部或内部链接;C++17 放开了此限制。
extern char const s03[] = "hi";
char const s11[] = "hi";
int main()
{
MyClass<s03> m03;
MyClass<s11> m11;
static char const s17[] = "hi";
MyClass<s17> m17;
}
非类型模板参数的实参可能是任何编译期表达式。
template<int I, bool B>
class C;
...
C<sizeof(int) + 4, sizeof(int)==4> c;
当表达式中使用了大于号,需要将整个表达式用小括号括起来。
C<42, sizeof(int) > 4> c;
C<42, (sizeof(int) > 4)> c;
非类型模板参数 auto
从 C++17 开始, 可以将非类型模板参数定义为 auto ,以接收任何允许作为非类型模板参数的类型。
#include <array>
#include <cassert>
template<typename T, auto Maxsize>
class Stack {
public:
using size_type = decltype(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)
{
}
template<typename T, auto Maxsize>
void Stack<T,Maxsize>::push (T const& elem)
{
assert(numElems < Maxsize);
elems[numElems] = elem;
++numElems;
}
template<typename T, auto Maxsize>
void Stack<T,Maxsize>::pop ()
{
assert(!elems.empty());
--numElems;
}
template<typename T, auto Maxsize>
T const& Stack<T,Maxsize>::top () const
{
assert(!elems.empty());
return elems[numElems-1];
}
从 C++14 开始,已经支持使用 auto 作为函数返回值。因此成员函数 size() 可以简写为:
auto size() const {
return numElems;
}
上述模板的使用:
int main()
{
Stack<int,20u> int20Stack;
Stack<std::string, 40> stringStack;
int20Stack.push(7);
std::cout << int20Stack.top() << '\n';
auto size1 = int20Stack.size();
stringStack.push("hello");
std::cout << stringStack.top() << '\n';
auto size2 = stringStack.size();
if (!std::is_same_v<decltype(size1), decltype(size2)>) {
std::cout << "size types differ" << '\n';
}
}
前面说过,非类型模板参数 auto 接收任何允许作为非类型模板参数的类型。
#include <iostream>
template<auto T>
class Message {
public:
void print() {
std::cout << T << '\n';
}
};
int main()
{
Message<42> msg1;
msg1.print();
static char const s[] = "hello";
Message<s> msg2;
msg2.print();
}
非类型模板 auto 的参数仍不能是浮点数。
Stack<int,3.14> sd;
使用 decltype(auto) 指定非类型模板参数的类型也是可以的。
template<decltype(auto) N>
class C {
...
};
int i;
C<(i)> x;
参考
|