概述
模板
1.函数模板
(1)使用 template 关键字引入模板
template void fun(T) {…}
1 函数模板的声明与定义
2 typename 关键字可以替换为 class ,含义相同
3 函数模板中包含了两对参数:函数形参 / 实参;模板形参 / 实参
(2)函数模板的显式实例化: fun(3)
1 实例化会使得编译器产生相应的函数(函数模板并非函数,不能调用)
2 编译期的两阶段处理
模板语法检查
模板实例化
3 模板必须在实例化时可见 – 翻译单元的一处定义原则
4 注意与内联函数的异同
#include <iostream>
template <typename T>
void fun(T input)
{
std::cout << input << std::endl;
}
int main()
{
fun<int>(3);
fun<double>(3.0);
}
(3)函数模板的重载
#include <iostream>
template <typename T>
void fun(T input)
{
std::cout << input << std::endl;
}
template <typename T, typename T2>
void fun(T input, T2 input2)
{
std::cout << input << std::endl;
std::cout << input2 << std::endl;
}
int main()
{
fun<int>(3);
fun<double>(3.0);
}
(4)模板实参的类型推导
如果函数模板在实例化时没有显式指定模板实参,那么系统会尝试进行推导
推导是基于函数实参(表达式)确定模板实参的过程,其基本原则与 auto 类型推导相似
1 函数形参是左值引用 / 指针:
忽略表达式类型中的引用
将表达式类型与函数形参模式匹配以确定模板实参
#include <iostream>
template <typename T>
void fun(T& input)
{
std::cout << input << std::endl;
}
int main()
{
int y = 3;
int& x = y;
fun(x);
/*
x -> int& -> int
input -> T&
*/
}
2 函数形参是万能引用
如果实参表达式是右值,那么模板形参被推导为去掉引用的基本类型
如果实参表达式是左值,那么模板形参被推导为左值引用,触发引用折叠
#include <iostream>
template <typename T>
void fun(T&& input)
{
std::cout << input << std::endl;
}
int main()
{
fun(3);
int y = 4;
fun(y); // T -> int&, int& && -> int&
}
3 函数形参不包含引用
忽略表达式类型中的引用
忽略顶层 const
数组、函数转换成相应的指针类型
#include <iostream>
template <typename T>
void fun(T input)
{
std::cout << input << std::endl;
}
int main()
{
fun(3);
int x = 3;
const int& ref = x;
fun(ref);
const int* const ptr = &x;
fun(ptr); //const int*
}
#include <iostream>
template <typename T>
void fun(T input, T input2)
{
std::cout << input << std::endl;
std::cout << input2 << std::endl;
}
int main()
{
fun<int>(2, 4.5);
}
(5)模板实参并非总是能够推导得到
1 如果模板形参与函数形参无关,则无法推导
2 即使相关,也不一定能进行推导,推导成功也可能存在因歧义而无法使用
template <typename T = double>
void fun(T x, T y)
{
}
int main()
{
fun(3, 5.0);
}
(6)在无法推导时,编译器会选择使用缺省模板实参
可以为任意位置的模板形参指定缺省模板实参–注意与函数缺省实参的区别
template <typename T = int>
void fun(unsigned x = sizeof(T))
{
}
int main()
{
fun(2);
}
#include <iostream>
#include <type_traits>
template <typename res = double, typename T>
res fun(T x, T y)
{
}
int main()
{
fun(2,5);
}
(7)显式指定部分模板实参
1 显式指定的模板实参必须从最左边开始,依次指定
2 模板形参的声明顺序会影响调用的灵活性
template <typename res = double, typename T>
res fun(T x, T y)
{
}
int main()
{
fun<int, int>(2,5);
}
template <typename res, typename T>
res fun(T x, T y)
{}
int main()
{
fun<int>(2,5);
}
(8)函数模板制动推导时会遇到的几种情况
1 函数形参无法匹配 SFINAE (替换失败并非错误)
template <typename T>
void fun(T x, T y){}
template <typename T, typename T2>
void fun(T x, T2 y){}
int main()
{
fun<int>(2,5.0);
}
2 模板与非模板同时匹配,匹配等级相同,此时选择非模板的版本
#include <iostream>
template <typename T, typename T2>
void fun(T x, T2 y)
{
std::cout << 1 << std::endl;
}
void fun(int x, double y)
{
std::cout << 2 << std::endl;
}
int main()
{
fun(2, 5.0);
}
3 多个模板同时匹配,此时采用偏序关系确定选择“最特殊”的版本
#include <iostream>
template <typename T, typename T2>
void fun(T x, T2 y)
{
std::cout << 1 << std::endl;
}
template <typename T>
void fun(T x, float y)
{
std::cout << 2 << std::endl;
}
int main()
{
fun(2,5.0f);
}
#include <iostream>
template <typename T>
void fun(T x) // A T可以匹配A,也可以匹配B*
{
std::cout << 1 << std::endl;
}
template <typename T>
void fun(T* x) // B* T*不可以匹配A,可以匹配B*,所以更“特殊”
{
std::cout << 2 << std::endl;
}
int main()
{
int x = 3;
fun(&x);
}
(9)函数模板的实例化控制
1 显式实例化定义: template void fun(int) / template void fun(int)
#include <iostream>
template <typename T>
void fun(T x)
{
std::cout << x << std::endl;
}
template void fun<int> (int x);
int main()
{
int x = 3;
fun<int>(x);
}
2 显式实例化声明: extern template void fun(int) / extern template void fun(int)
3 注意一处定义原则
4 注意实例化过程中的模板形参推导
#include <iostream>
template <typename T>
void fun(T x)
{
std::cout << x << std::endl;
}
template<typename T>
void fun(T * x)
{
std::cout << x <<std::endl;
}
template void fun(int * x);
template void fun<int *>(int * x);
int main()
{
}
(10)函数模板的 ( 完全 ) 特化: template<> void f(int) / template<> void f(int)
1 并不引入新的(同名)名称,只是为某个模板针对特定模板实参提供优化算法
2 注意与重载的区别
3 注意特化过程中的模板形参推导
#include <iostream>
template <typename T>
void fun(T x)
{
std::cout << x << std::endl;
}
template<>
void fun(int x)
{
std::cout << x + 1<<std::endl;
}
int main()
{
fun(3.0);
}
(11)避免使用函数模板的特化
1 不参与重载解析,会产生反直觉的效果
#include <iostream>
template <typename T>
void fun(T x)
{
std::cout << 1 << std::endl;
}
template <typename T>
void fun(T* x)
{
std::cout << 2 << std::endl;
}
template<>
void fun(int* x)
{
std::cout << 3 <<std::endl;
}
int main()
{
int x;
fun(&x);
}
#include <iostream>
template <typename T>
void fun(T x)
{
std::cout << 1 << std::endl;
}
template <typename T>
void fun(T* x)
{
std::cout << 2 << std::endl;
}
template<>
void fun<int*>(int* x)
{
std::cout << 3 <<std::endl;
}
int main()
{
int x;
fun(&x);
}
2 通常可以用重载代替
#include <iostream>
template <typename T>
void fun(T x)
{
std::cout << 1 << std::endl;
}
template <typename T>
void fun(T* x)
{
std::cout << 2 << std::endl;
}
void fun(int* x)
{
std::cout << 3 <<std::endl;
}
int main()
{
int x;
fun(&x);
}
3 一些不便于重载的情况:无法建立模板形参与函数形参的关联
#include <iostream>
template <typename T, typename res>
res fun(T x)
{
std::cout << 1 << std::endl;
return res{};
}
template <typename T>
void fun(T* x)
{
std::cout << 2 << std::endl;
}
int main()
{
int x;
fun(&x);
}
使用 if constexpr 解决
#include <iostream>
#include <type_traits>
template <typename res, typename T>
res fun(T x)
{
if constexpr(std::is_same_v<res, int>)
{
std::cout << 1 << std::endl;
} else
{
std::cout << 2 << std::endl;
}
return res{};
}
int main()
{
int x;
fun<int>(&x);
fun<float>(&x);
}
引入"假"函数形参
通过类模板特化解决
(12)(C++20) 函数模板的简化形式:使用 auto 定义模板参数类型
1 优势:书写简捷
2 劣势:在函数内部需要间接获取参数类型信息
#include <iostream>
void fun(auto x)
{
}
int main()
{
int x;
fun(&x);
}
2.类模板与成员函数模板
(1)使用 template 关键字引入模板
template class B {…};
template <typename T>
class B
{
};
int main()
{
B<int> x;
B<char> y;
}
1 类模板的声明与定义 翻译单元的一处定义原则
2 成员函数只有在调用时才会被实例化
#include <iostream>
template <typename T>
class B
{
public:
void fun(T input)
{
std::cout << input << std::endl;
}
};
struct Str{};
int main()
{
B<int> x;
x.fun(3);
B<Str> y;
}
3 类内类模板名称的简写
#include <iostream>
template <typename T>
class B
{
public:
auto fun()
{
return B{}; //return B<T>{};
}
};
struct Str{};
int main()
{
B<int> x;
x.fun();
}
4 类模板成员函数的定义(类内、类外)
#include <iostream>
template <typename T>
class B
{
public:
void fun();
};
template<typename T>
void B<T>::fun()
{
}
int main()
{
B<int> x;
x.fun();
}
(2)成员函数模板
1 类的成员函数模板
#include <iostream>
class B
{
public:
template <typename T>
void fun()
{
}
};
int main()
{
B x;
x.fun<int>();
}
#include <iostream>
class B
{
public:
template <typename T>
void fun();
};
template <typename T>
void B::fun() {
}
int main()
{
B x;
x.fun<int>();
}
2 类模板的成员函数模板
#include <iostream>
template <typename T>
class B
{
public:
template <typename T2>
void fun()
{
}
};
int main()
{
B<int> x;
x.fun<float>();
}
#include <iostream>
template <typename T>
class B
{
public:
template <typename T2>
void fun();
};
template <typename T>
template <typename T2>
void B<T>::fun()
{
}
int main()
{
B<int> x;
x.fun<float>();
}
(3)友元函数(模板)–使用较少
1 可以声明一个函数模板为某个类(模板)的友元
2 C++11 支持声明模板参数为友元
#include <iostream>
template <typename T>
class B
{
public:
template <typename T2>
friend void fun()
{
}
};
int main()
{
B<int> x;
}
#include <iostream>
template <typename T>
class B
{
public:
friend void fun(B input)
{
std::cout << input.x << std::endl;
}
private:
int x = 3;
};
int main()
{
B<int> val;
fun(val);
}
(4)类模板的实例化
与函数实例化很像,可以实例化整个类,或者类中的某个成员函数
namespace N
{
template<class T>
class Y // template definition
{
void mf() {}
};
}
// template class Y<int>; // error: class template Y not visible in the global namespace
using N::Y;
// template class Y<int>; // error: explicit instantiation outside
// of the namespace of the template
template class N::Y<char*>; // OK: explicit instantiation
template void N::Y<double>::mf(); // OK: explicit instantiation
(5)类模板的(完全)特化 / 部分特化(偏特化)
特化版本与基础版本可以拥有完全不同的实现
完全特化:
#include <iostream>
template <typename T>
class B
{
public:
void fun()
{
std::cout << 1 << std::endl;
}
};
template <>
class B<int>
{
public:
void fun()
{
std::cout << 2 << std::endl;
}
};
int main()
{
B<int> x;
x.fun();
}
部分特化:
#include <iostream>
template <typename T, typename T2>
class B
{
public:
void fun()
{
std::cout << 1 << std::endl;
}
};
template <typename T2>
class B<int, T2>
{
public:
void fun()
{
std::cout << 2 << std::endl;
}
};
int main()
{
B<int, double> x;
x.fun();
}
#include <iostream>
template <typename T>
class B
{
public:
void fun()
{
std::cout << 1 << std::endl;
}
};
template <typename T>
class B<T*>
{
public:
void fun()
{
std::cout << 2 << std::endl;
}
};
int main()
{
B<int*> x;
x.fun();
}
(6) 类模板的实参推导(从 C++17 开始)
1 基于构造函数的实参推导
#include <iostream>
template <typename T>
class B
{
public:
B(T input){}
void fun()
{
std::cout << 1 << std::endl;
}
};
int main()
{
B x(3);
x.fun();
}
2 用户定义的推导指引
// 模板的声明
template<class T>
struct container
{
container(T t) {}
template<class Iter>
container(Iter beg, Iter end);
};
// 额外的推导指引
template<class Iter>
container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;
// 使用
container c(7); // OK:用隐式生成的指引推导出 T=int
std::vector<double> v = {/* ... */};
auto d = container(v.begin(), v.end()); // OK:推导出 T=double
container e{5, 6}; // 错误:std::iterator_traits<int>::value_type 不存在
3 注意:引入实参推导并不意味着降低了类型限制!
4 C++ 17 之前的解决方案:引入辅助模板函数
#include <iostream>
#include <utility>
template <typename T1, typename T2>
std::pair<T1, T2> make_pair(T1 val1, T2 val2)
{
return std::pair<T1, T2>(val1, val2);
}
int main()
{
auto x = make_pair(3, 3.14);
}
3.Concepts
(1)模板的问题:没有对模板参数引入相应的限制
1 参数是否可以正常工作,通常需要阅读代码进行理解
2 编译报错友好性较差 (vector<int&>)
(2)( C++20 ) Concepts :编译期谓词,基于给定的输入,返回 true 或 false
#include <iostream>
#include <type_traits>
template <typename T>
concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;
int main()
{
std::cout << IsAvail<int> <<std::endl;
std::cout << IsAvail<char> << std::endl;
}
1 与 constraints ( require 从句)一起使用限制模板参数
2 通常置于表示模板形参的尖括号后面进行限制
#include <iostream>
#include <type_traits>
template <typename T>
concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;
template <typename T>
requires IsAvail<T>
void fun(T input)
{
}
int main()
{
fun(3);
//fun(true);
}
(3)Concept 的定义与使用
1 包含一个模板参数的 Concept
使用 requires 从句
template <typename T>
requires IsAvail<T>
void fun(T input)
{
}
直接替换 typename
template <IsAvail T>
void fun(T input)
{
}
2 包含多个模板参数的 Concept
用做类型 constraint 时,少传递一个参数,推导出的类型将作为首个参数
#include <iostream>
#include <type_traits>
template <typename T, typename T2>
concept IsAvail = std::is_same_v<T, T2>;
template <typename T, typename T2>
requires IsAvail<T, T2>
void fun(T input, T2 input2)
{
}
int main()
{
fun(3, 5);
}
#include <iostream>
#include <type_traits>
template <typename T, typename T2>
concept IsAvail = std::is_same_v<T, T2>;
template <typename T>
requires IsAvail<T, int>
void fun(T input)
{
}
int main()
{
fun(3);
}
#include <iostream>
#include <type_traits>
template <typename T, typename T2>
concept IsAvail = std::is_same_v<T, T2>;
template <IsAvail<int> T>
requires IsAvail<T, int>
void fun(T input)
{
}
int main()
{
fun(3);
}
(4)requires 表达式
1 简单表达式:表明可以接收的操作
template <typename T>
concept Addable = requires(T a, T b)
{
a + b;
};
template <Addable T>
auto fun(T x, T y)
{
return x + y;
}
int main()
{
fun(3, 5);
}
2 类型表达式:表明是一个有效的类型
template <typename T>
concept Avail = requires
{
typename T::inter;
};
template <Avail T>
auto fun(T x)
{
}
struct Str
{
using inter = int;
};
int main()
{
fun(Str{});
}
3 复合表达式:表明操作的有效性,以及操作返回类型的特性
//明明一样却还是报错。。。。。。。。。。。。
#include <iostream>
#include <type_traits>
template <typename T>
concept Avail =
requires (T x)
{
{x + 1} -> int;
};
template <Avail T>
auto fun(T x)
{
}
int main()
{
fun(3);
}
4 嵌套表达式:包含其它的限定表达式
(5)requires 从句会影响重载解析与特化版本的选取
只有 requires 从句有效而且返回为 true 时相应的模板才会被考虑
requires 从句所引入的限定具有偏序特性,系统会选择限制最严格的版本
#include <iostream>
#include <type_traits>
template <typename T>
requires std::is_same_v<T, float>
void fun(T input)
{
std::cout << 1 << "n";
}
template <typename T>
requires std::is_same_v<T, int>
void fun(T input)
{
std::cout << 2 << "n";
}
int main()
{
fun(3);
}
#include <iostream>
#include <type_traits>
template <typename T>
concept C1 = std::is_same_v<T, int>;
template <typename T>
concept C2 = std::is_same_v<T, int> || std::is_same_v<T, int>;
template <C1 T>
void fun(T input)
{
std::cout << 1 << "n";
}
template <C2 T>
void fun(T input)
{
std::cout << 2 << "n";
}
int main()
{
fun(3);
}
(6)特化小技巧:在声明中引入 "A||B” 进行限制,之后分别针对 A 与 B 引入特化
#include <iostream>
#include <type_traits>
template <typename T>
requires std::is_same_v<T, int> || std::is_same_v<T, float>
class B;
template <>
class B<int> {};
template <>
class B<float> {};
int main()
{
B<double> x;
}
4.模板相关内容
(1)数值模板参数与模板模板参数
1 模板可以接收(编译期常量)数值作为模板参数
template class Str;
#include <iostream>
template<int a>
int fun(int x)
{
return x + a;
}
int main()
{
std::cout << fun<3>(5);
}
template <typename T, T value> class Str;
#include <iostream>
template<typename T, T a>
int fun(int x)
{
return x + a;
}
int main()
{
std::cout << fun<int, 3>(5);
}
(C++ 17) template class Str;
#include <iostream>
template<auto a>
int fun()
{
}
int main()
{
fun<3>();
fun<true>();
}
(C++ 20) 接收字面值类对象与浮点数作为模板参数
2 接收模板作为模板参数
template <template class C> class Str;
#include <iostream>
template<typename T>
class C{};
template <template<typename T> class T2>
void fun()
{
}
int main()
{
fun<C>();
}
(C++17) template <template typename C> class Str;
C++17 开始,模板的模板实参考虑缺省模板实参
(2)别名模板与变长模板
1 可以使用 using 引入别名模板
为模板本身引入别名
#include <iostream>
template<typename T>
using MyType = T;
int main()
{
MyType<int> t;
}
template<class T>
struct Alloc { };
template<class T>
using Vec = vector<T, Alloc<T>>; // 类型标识为 vector<T, Alloc<T>>
Vec<int> v; // Vec<int> 等同于 vector<int, Alloc<int>>
为类模板的成员引入别名
template<typename T>
struct B
{
using TP = T*;
};
template <typename T>
using MyPointer = typename B<T>::TP;
int main()
{
MyPointer<int> t;
}
别名模板不支持特化,但可以基于类模板的特化引入别名,以实现类似特化的功能
2 变长模板( Variadic Template )
变长模板参数与参数包
#include <iostream>
template<int... a>
void fun()
{
}
int main()
{
fun<1, 2, 3>();
}
#include <iostream>
template<typename ... a>
void fun()
{
}
int main()
{
fun<double, int, char>();
}
变长模板参数可以是数值、类型或模板
sizeof… 操作
#include <iostream>
template<typename ...T>
void fun(T... args)
{
std::cout << sizeof ...(args) << std::endl;
}
int main()
{
fun(2, 4.5, 'c');
}
注意变长模板参数的位置
(3)包展开与折叠表达式
template<typename ...T>
void fun(T... args) //T... -> int, double, char
{
std::cout << sizeof ...(args) << std::endl;
}
int main()
{
fun(2, 4.5, 'c');
}
1 (C++11) 通过包展开技术操作变长模板参数
– 包展开语句可以很复杂,需要明确是哪一部分展开,在哪里展开
2 (C++17) 折叠表达式 (cpp reference)
– 基于逗号的折叠表达式应用
– 折叠表达式用于表达式求值,无法处理输入(输出)是类型与模板的情形
#include <iostream>
template<typename ...T>
void fun(T... args)
{
((std::cout << args << std::endl), ...);
}
int main()
{
fun(2, 4.5, 'c', "hello");
}
(4)完美转发与 lambda 表达式模板
1 (C++11) 完美转发: std::forward 函数
通常与万能引用结合使用
同时处理传入参数是左值或右值的情形
#include <iostream>
#include <utility>
void g(int&)
{
std::cout << "l-reference" << std::endl;
}
void g(int&&)
{
std::cout << "r-reference" << std::endl;
}
template <typename T>
void fun(T&& input)
{
std::cout << "hellon";
g(std::forward<T>(input));
}
int main()
{
int x = 3;
fun(x);
fun(3);
}
2 (C++20) lambda表达式模板
(5)消除歧义与变量模板
1 使用 typename 与 template 消除歧义
使用 typename 表示一个依赖名称是类型而非静态数据成员
#include <iostream>
struct Str
{
using internal = int;
};
struct Str2
{
//inline const static internal = 100;
};
template<typename T>
void fun()
{
typename T::internal* p;
}
int main()
{
fun<Str>();
//fun<Str2>();
}
使用 template 表示一个依赖名称是模板
template 与成员函数模板调用
2 (C++14) 变量模板
template T pi = (T)3.1415926;
最后
以上就是暴躁秋天为你收集整理的【深蓝学院C++】第12章 模板 笔记的全部内容,希望文章能够帮你解决【深蓝学院C++】第12章 模板 笔记所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复