函数模板&类模板
(1). 模板形参
模板形参用来给模板类型提供额外信息,可以是用来提供类型,也可以用来提供特定类型下的值.
#include <stdio.h>
#include <stdlib.h>
template<class T, int X>
class Tem{
public:
Tem(T nT):m_nT(nT),m_nX(X) {
}
T m_nT;
int m_nX;
};
int main(){
Tem<int, 10> stT(11);
printf("T_%d,X_%d\n", stT.m_nT, stT.m_nX);
return 0;
}
(2). 类内作用域,类外作用域
#include <stdio.h>
#include <stdlib.h>
template<class T, int X>
class Tem{
public:
Tem(T nT):m_nT(nT),m_nX(X){
}
int fun1(Tem){
return 0;
}
int fun2(Tem);
T m_nT;
int m_nX;
};
template<class T, int X>
int Tem<T, X>::fun2(Tem){
return 0;
}
int main(){
Tem<int, 10> stT(11);
printf("T_%d,X_%d\n", stT.m_nT, stT.m_nX);
return 0;
}
a. 类内作用域
出现在模板类型定义的{}
之间的部分算类内作用域.
在模板类型外部定义模板函数实现时,形参列表及函数体算类内作用域.
在类内作用域引用类型自身时,默认补上模板形参列表.
如上述fun1
的Tem
等价于Tem<T,X>
.
b. 类外作用域
不属于类内的,属于类外.
模板类型推断
在类型推断过程中:
(1). 顶层const
实参,可以推断为不含顶层const
.
(2). 在模板参数未限定为引用时,可以将数组或函数实参,推断为数组首元素指针/函数指针.
模板函数的模板类型:
(1). 可以依赖推断,此时对模板函数执行调用时使用function(...)
形式,
(2). 可以不依赖推断,此时对模板函数执行调用时使用function<...>(...)
形式.
此时执行顺序:
a. 依据function<...>
得到一个实例化普通函数.
b. 按实例化普通函数执行函数实参,执行形参匹配.
重载与模板
类模板
类模板不支持重载,但支持全特化,偏特化.通过特化可以达到类型函数重载的效果.
#include <iostream>
#include <string>
// 通用模板类
template <typename T1, typename T2>
class MyTemplate {
public:
void print() {
std::cout << "General template for T1 and T2" << std::endl;
}
};
// 全特化版本,针对int和double
template <>
class MyTemplate<int, double> {
public:
void print() {
std::cout << "Specialized for int and double" << std::endl;
}
};
// 偏特化版本,针对第一个类型为std::string的情况
template <typename T2>
class MyTemplate<std::string, T2> {
public:
void print() {
std::cout << "Partially specialized for std::string as the first type" << std::endl;
}
};
int main() {
MyTemplate<int, double> a;
a.print(); // 输出:Specialized for int and double
MyTemplate<std::string, int> b;
b.print(); // 输出:Partially specialized for std::string as the first type
MyTemplate<char, float> c;
c.print(); // 输出:General template for T1 and T2
return 0;
}
函数模板
函数模板的重载并不是C++
语言直接支持的概念,因为函数模板本身就是一种泛型编程工具,用于处理不同类型的数据。然而,你可以通过定义多个具有不同参数类型的函数模板来模拟重载的行为。当编译器遇到函数调用时,它会根据提供的参数类型选择最合适的模板进行实例化。
另一方面,函数模板的全特化和偏特化与类模板的全特化和偏特化类似。全特化是为函数模板的所有模板参数提供具体类型,而偏特化则是为函数模板的部分模板参数提供具体类型。
下面是一个函数模板重载、全特化和偏特化的例子:
(1). 函数模板重载模拟
虽然C++
没有直接支持函数模板重载,但你可以通过定义多个具有不同参数类型的函数模板来模拟这一行为:
template <typename T>
void printValue(T value) {
std::cout << "General template for value: " << value << std::endl;
}
template <typename T>
void printValue(T* ptr) {
std::cout << "General template for pointer: " << ptr << endl;
}
在这个例子中,printValue
函数模板有两个版本,一个接受值类型,另一个接受指针类型。当传递一个值或指针给printValue
时,编译器会根据参数类型选择正确的模板版本。
(2). 函数模板全特化
全特化是为函数模板的所有模板参数提供具体类型:
template <typename T>
void printValue(T value) {
std::cout << "General template for value: " << value << std::endl;
}
// 全特化,针对int类型
template <>
void printValue<int>(int value) {
std::cout << "Specialized for int: " << value << std::endl;
}
在这个例子中,printValue
函数模板有一个全特化版本,专门用于处理int
类型的参数。当传递一个int
给printValue
时,将使用全特化版本。
(3). 函数模板偏特化
偏特化是为函数模板的部分模板参数提供具体类型。但请注意,函数模板的偏特化在C++
标准中是不允许的,因为它会导致模板参数解析的歧义。不过,你可以通过重载或默认参数来模拟偏特化的行为。下面是一个使用默认参数及重载来模拟函数模板偏特化的例子:
template <typename T1, typename T2 = int>
void printPair(T1 a, T2 b) {
std::cout << "General template for pair: " << a << ", " << b << std::endl;
}
// 模拟偏特化,当T2为double时
template <typename T1>
void printPair(T1 a, double b) {
std::cout << "Specialized for double as the second type: " << a << ", " << b << std::endl;
}
在这个例子中,printPair
函数模板有一个默认参数T2
,其默认类型为int
。我们还定义了一个重载版本,它接受一个任意类型T1
和一个double
类型作为第二个参数。当第二个参数为double
时,将使用重载版本,否则将使用通用模板版本。这模拟了偏特化的行为,尽管它并不是真正的偏特化。
可变参数模板
C++
中的可变模板参数允许你定义一个模板,它可以接受任意数量和类型的参数。这提供了极大的灵活性,使得模板函数或类可以处理不同类型和数量的参数。
以下是如何使用可变模板参数的简单示例:
(1). 可变模板参数函数
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl;
}
int main() {
print("Hello, ", "world!", 123); // 输出: Hello, world! 123
return 0;
}
在上面的例子中,print
函数使用了可变模板参数Args...
,它可以接受任意数量和类型的参数。然后,我们使用折叠表达式(std::cout << ... << args)
来打印所有参数。
(2). 可变模板参数类
template<typename... Args>
class Tuple {
public:
Tuple(Args... args) : data_{args...} {}
// ... 其他成员函数和操作符重载 ...
private:
std::tuple<Args...> data_;
};
int main() {
Tuple<int, double, std::string> t(1, 2.3, "Hello");
return 0;
}
在这个例子中,我们定义了一个Tuple
类,它使用可变模板参数来存储任意数量和类型的元素。data_
成员是一个std::tuple
,它也可以接受可变模板参数。
注意:
可变模板参数通常与递归模板、完美转发、折叠表达式等技术结合使用,以实现更复杂的功能。
(3). 使用C++
可变模板参数与递归模板、完美转发、折叠表达式的实际例子。
这个例子实现了一个通用的print
函数,它可以接受任意数量和类型的参数,并将它们打印到标准输出。
#include <iostream>
#include <utility> // for std::forward
// 基础情况:当没有参数时,直接返回
template<>
void print() {
std::cout << std::endl;
}
// 递归情况:打印第一个参数,然后递归调用print打印剩余参数
template<typename First, typename... Rest>
void print(First&& first, Rest&&... rest) {
std::cout << std::forward<First>(first);
if constexpr (sizeof...(rest) > 0) {
std::cout << " ";
print(std::forward<Rest>(rest)...); // 递归调用,使用完美转发和折叠表达式
} else {
std::cout << std::endl;
}
}
int main() {
// 使用print函数打印不同类型的参数
print("Hello", "world", 42, 3.14, '!');
// 输出: Hello world 42 3.14 !
// 也可以传递l-value和r-value引用
int a = 10;
const int b = 20;
print(a, std::move(a), b);
// 输出: 10 10 20
// 注意:std::move(a)并不会改变a的值,这里只是为了演示完美转发
return 0;
}
在这个例子中:
a. print
函数被定义为一个可变模板参数函数,可以接受任意数量和类型的参数。
b. 特化了一个基本情况,当没有参数时,直接打印一个换行符。
c. 递归情况中,print
函数首先打印第一个参数,然后递归调用自身来打印剩余的参数。递归调用使用了完美转发(std::forward
)来确保参数的原始值类别(l-value
或r-value
)被保留。
d. if constexpr
语句用于检查是否还有剩余参数需要打印。如果有,就在第一个参数和后续参数之间添加一个空格。
折叠表达式(print(std::forward<Rest>(rest)...);
)用于递归地调用print
函数,并将剩余的参数作为参数包传递。
这个print
函数是一个很好的例子,展示了可变模板参数、递归模板、完美转发和折叠表达式如何协同工作来创建一个通用且灵活的函数。通过使用这些技术,我们可以编写出能够处理任意数量和类型参数的函数,而无需编写大量的重载函数或模板特化。
类模板偏特化实例
#include <stdio.h>
// 模板偏特化测试
template <typename T>
class A {
public:
A(T t) : a(t) {
printf("1\n");
}
void add() {
a++;
}
T get() {
return a;
}
T a;
};
template <typename T>
class A<T*> {
public:
A(T* t) : a(t) {
printf("2\n");
}
void add() {
a++;
}
T* get() {
return a;
}
T* a;
};
template <typename T>
class A<T&> {
public:
A(T& t) :a(t) {
printf("3\n");
}
void add() {
a++;
}
T get() {
return a;
}
T& a;
};
int main() {
int t = 10;
int at[] = {10, 20, 30};
int* pa = at;
A<int> a1(10);
A<int*> a2(pa);
A<int&> a3(t);
a1.add();
printf("%d\n", a1.get());
a2.add();
printf("%lld\n", (long long)a2.get());
printf("&at[1]=%lld\n", &at[1]);
a3.add();
printf("%d\n", a3.get());
printf("t=%d\n",t);
int i;
scanf("%d", &i);
}
typename
- 在定义模板类形参中用typename
定义模板类型形参
- 通过模板类型访问其成员时,默认成员是数据成员[变量/函数].若向告诉编译器成员是类型,需用typename
#include <stdio.h>
template <typename T>
class A {
public:
class Node {
public:
T t1;
T t2;
};
A(T tt) : t(tt) {
printf("1\n");
nt.t1 = t+1;
nt.t2 = t+2;
}
T get();
Node getn();
T t;
Node nt;
};
template<typename T>
T A<T>::get() {
return t;
}
template<typename T>
typename A<T>::Node A<T>::getn() {
return nt;
}
int main() {
A<int> a(10);
printf("%d\n", a.get());
int i;
//scanf("%d", &i);
A<int>::Node nt = a.getn();
printf("%d %d\n", nt.t1, nt.t2);
scanf("%d", &i);
}