typename
typename 的引入是用来修饰模板类中的标识符是一个类型。
template<typename T>
class MyClass {
void foo() {
// typename 修饰,表示类型T中的一个SubType类型的指针变量 ptr;
typename T::SubType* ptr;
// 类型T中的名为 SubType 的静态数据成员(or 枚举数常量) 乘以 ptr 这个变量
T::SubType* ptr;
}
};
没有typename修饰,编译器无法将Subtype 识别为一个类型(C++20 貌似不需要typename进行修饰了?!)
typename 最常见的一种应用就是在范型代码中定义标准容器的迭代器:
#include <iostream>
#include <vector>
#include <unordered_set>
template<typename T>
void printColl(const T& coll) {
typename T::const_iterator pos; // STL容器中的const_iterator迭代器
typename T::const_iterator end = coll.end(); // 指向容器末尾的迭代器
for(pos = coll.begin(); pos != end; ++pos) {
std::cout << *pos << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> ve{5,4,3,2,1};
printColl(ve);
std::unordered_set<int> us{5,4,3,2,1};
printColl(us);
std::array<int, 5> arr{5,6,7,8,9};
printColl(arr);
return 0;
}
使用this->
继承自类模板的派生类模板,即使继承了基类的成员,但是仍需要使用全限定符来修饰
#include <iostream>
// 基类类模板
template<typename T>
class Base {
public:
virtual void bar() {
std::cout << "Base::bar()" << std::endl;
}
};
// 派生类类模板
template<typename T>
class Derived : public Base<T> {
public:
void bar() override {
std::cout << "Derived::bar()" << std::endl;
}
void foo() {
// bar(); // 错误,对于继承自类模板的派生类,必须使用基类模板的全限定名来访问其成员
Base<T>::bar(); //正确, 调用基类模板的成员函数
this->bar(); // 正确,调用派生类模板的成员函数
}
};
int main() {
Derived<int> d;
d.foo();
}
原始数组和字符串字面量的模板
将原始数组或字符串字面值传递给模板时,要十分注意:
#include <typeinfo>
#include <iostream>
// 通过传入变量,来判断变量的类型
// 注意,这里的类型name是mangled 名称的标准方法, int 会记做 i, double会记做 d
// 模板参数值传递时,对于 字符串字面量和字符数组,会衰变成 const char*
template <typename T>
void printTypeOfVariable(T t) {
std::cout << "The type of the variable is: " << typeid(t).name() << std::endl;
}
// 注意,如果模板参数是引用,在传入字符串字面量或者字符数组时
// 参数类型不会衰变成 const char*, 而是保持字符数组类型 const char[*]
template <typename T>
void printTypeOfVariableByRefence(T& t) {
std::cout << "The type of the variable is: " << typeid(t).name() << std::endl;
}
int main() {
int i = 0;
double d = 3.14;
std::string s = "Hello, World!";
// 如果你想通过变量来获取类型(但这通常不是必需的)
printTypeOfVariable(i); // 返回 i ,表示int类型
printTypeOfVariable(d); // 返回 d, 表示double类型
printTypeOfVariable(s); // NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE 表示std::string类型
printTypeOfVariable("name"); // 返回 PKc 表示const char*类型
printTypeOfVariableByRefence("name"); // 返回A5_c, 表示const char[5]
return 0;
}
利用这种特性,可以针对引用类型模板参数,可以设计专门处理原始字符串数组和原始字面量的模板函数:
#include <iostream>
// 模板函数:比较两个原始字符数组(or 字符串字面量)的大小
// 引用限定的数组类型 T(&a)[N], T(&b)[M]
template<std::size_t N, std::size_t M, typename T>
bool less(T(&a)[N], T(&b)[M]) {
for(auto i = 0; i < N && i < M; ++i) {
if(a[i] < b[i]) return true;
if(a[i] > b[i]) return false;
}
return N < M;
}
int main() {
char a[] = "abc";
char b[] = "abcd";
std::cout << std::boolalpha << less(a, b) << std::endl;
std::cout << less("aa", "bac") << std::endl;
std::cout << less("vd", "caa") << std::endl;
// std::cout << less(std::string("aa"), std::string("aa")) << std::endl; // 无法匹配,不接受string 类型
return 0;
}
变量模板
在C++14及以后版本中,变量模板是一种允许程序员定义和实例化的模板化变量。变量模板与函数模板类似,但应用于变量,这意味着它可以基于模板参数生成不同的变量实例,这些实例可能是不同类型的数据。
传统的变量只能存储单一类型的数据,而变量模板允许你根据模板参数来定义一组具有相同名称但不同类型或不同值的变量。声明变量模板的基本语法如下:
#include <iostream>
// 定义一个变量模板, 用constexpr修饰,表示常量
template<typename T>
constexpr T PI{3.1415926};
// 定义一个无参的变量模板
template<typename T>
T Val{};
// 根据变量自动推导类型
template<auto T>
constexpr decltype(T) DVal = T;
int main() {
std::cout<< PI<double> << std::endl; // <> 不能省略
std::cout<< PI<float> << std::endl;
Val<int> = 5; // 赋值int
std::cout<< Val<int> << std::endl;
Val<double> = 2.5; // 赋值double
std::cout<< Val<double> << std::endl;
std::cout<< DVal<2> << std::endl;
std::cout<< DVal<'c'> << std::endl;
return 0;
}
变量模板还可以定义类模板成员的变量,可以为模板类的不同特化,定义不同的值:
#include <iostream>
template<typename T>
class Base{
public:
static constexpr int max = 2;
};
template<typename T>
int BaseMax = Base<T>::max;
int main() {
Base<int> a;
auto i = BaseMax<int>; // 相当于int i = Base<int>::max;
int j = Base<int>::max; // 等价于上面
std::cout << i <<" "<< j << std::endl;
return 0;
}