模板参数只能够是某种具体的类型么?答案是否定的。除了具体的类型,常规数据值也可以作为模板参数,如下:
#include <cstdio>
#include <cassert>
#include <functional>
template <typename T, size_t S>
class Array final
{
public:
Array(void) : m_Data(new T[S]) {}
T &operator[](size_t i) {
assert(i < S);
return m_Data[i];
}
size_t Size(void) const {
return S;
}
Array(const Array &) = delete;
Array(Array &&) = delete;
Array &operator=(const Array &) = delete;
Array &operator=(Array &&) = delete;
private:
T *m_Data;
};
template <typename T, size_t S, typename F>
void Visit(Array<T, S> &array, F func)
{
for (size_t i = 0; i < array.Size(); ++i) {
func(array[i]);
}
}
int main(int argc, char **argv)
{
Array<int, 10> array;
auto val_assign = [&](int &val) -> void {
val = 10;
};
Visit(array, val_assign);
auto val_print = [&](int &val) -> void {
printf("%d\n", val);
};
Visit(array, val_print);
return 0;
}
使用非类型模板参数是有限制的。通常它们只能是整形常量(包含枚举),指向 objects/functions/members 的指针,objects 或者 functions 的左值引用,或者是 std::nullptr_t (类型是 nullptr)。下面的源码都是无法通过编译的:
template <double pi = 3.14>
double side(double radius)
{
return pi * radius * radius;
}
template <std::string s>
std::string cat(std::string &str)
{
return str + s;
}
template <std::string *p>
std::string print(void)
{
printf("%s\n", *p->c_str());
}
非类型模板参数在c++中的使用场景还是比较多的,例如:std::array,std::get(读取std::tuple)。
auto作为非模板类型参数的类型
从C++17开始,可以使用auto代替非模板类型参数的具体类型,并且可以通过decltype对非模板类型参数的具体类型推断,然后使用,下面是一个简单的例子:
#include <vector>
#include <cstdio>
#include <cassert>
template <typename T, auto MAX_SIZE>
class MyArray final
{
private:
using size_type = decltype(MAX_SIZE);
public:
MyArray(void) : m_Data(new T[MAX_SIZE]), m_Index(0) {}
~MyArray(void) {
if (m_Data == nullptr) {
delete[] m_Data;
m_Data = nullptr;
}
}
void push(const T &data) {
assert(m_Index < MAX_SIZE);
m_Data[m_Index++] = data;
}
void pop(void) {
assert(m_Index > 0);
m_Index--;
}
T top(void) const {
assert(m_Index > 0);
return m_Data[m_Index - 1];
}
bool empty(void) const {
return m_Index == 0;
}
size_type size(void) const {
return m_Index;
}
MyArray(const MyArray &) = default;
MyArray(MyArray &&) = default;
MyArray &operator=(const MyArray &) = default;
MyArray &operator=(MyArray &&) = default;
private:
T *m_Data;
size_type m_Index;
};
int main(int argc, char **argv)
{
MyArray<int, 15> arr1;
for (int i = 0; i < 15; ++i) {
arr1.push(i);
}
MyArray<int, 100u> arr2;
for (int i = 0; i < 100; ++i) {
arr2.push(i);
}
return 0;
}
关于上面的例子,如何确认arr1和arr2的非模板类型参数的类型是否相同?可以通过std::is_same::value(c++11之后)或std::is_same_v(c++17之后),代码如下:
if (std::is_same<decltype(arr1.size()), decltype(arr2.size())>::value) {
printf("size type same !\n");
}
else {
printf("size type diff");
}
但需要注意,关于非类型模板参数的限制仍然存在:不可以将浮点型或者 class 类型的对象用于非类型模板参数。
非类型模板参数支持auto有什么意义?书中给出的答案,使得泛化更加灵活。下面的代码或许能够说明这一点:
template<unsigned char v>
void MyPrint(void)
{
printf("%u\n", v);
}
......
MyPrint<10>(); //OK
MyPrint<1000>(); //invalid explicitly-specified argument for template parameter 'v'
......
template<auto v>
void MyPrint2(void)
{
printf("%u\n", v);
}
......
MyPrint2<10>();
MyPrint2<1000>();
但是个人始终觉得这个说法有些勉强,非模板类型参数的类型设置为uint64_t,无论10,还是1000,都是用uint64_t,会有什么问题么?