惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
V
Visual Studio Blog
The GitHub Blog
The GitHub Blog
Apple Machine Learning Research
Apple Machine Learning Research
J
Java Code Geeks
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
人人都是产品经理
人人都是产品经理
H
Heimdal Security Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Cyberwarzone
Cyberwarzone
Google DeepMind News
Google DeepMind News
雷峰网
雷峰网
Hacker News: Ask HN
Hacker News: Ask HN
Webroot Blog
Webroot Blog
Microsoft Azure Blog
Microsoft Azure Blog
MyScale Blog
MyScale Blog
A
About on SuperTechFans
V2EX - 技术
V2EX - 技术
小众软件
小众软件
博客园 - Franky
博客园 - 司徒正美
P
Privacy International News Feed
爱范儿
爱范儿
U
Unit 42
博客园 - 叶小钗
The Hacker News
The Hacker News
C
Check Point Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Simon Willison's Weblog
Simon Willison's Weblog
N
News and Events Feed by Topic
D
Docker
T
Threatpost
MongoDB | Blog
MongoDB | Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
H
Help Net Security
L
LINUX DO - 最新话题
Security Latest
Security Latest
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
A
Arctic Wolf
Spread Privacy
Spread Privacy

Jun's Blog

2023 年度总结 OS 学习记 之 XV6 如何编译 GraalVM LLVM 中端优化之 InstCombine C++ 中 inline 关键字的语义 链接与库 C++ 移动语义基础 如何优化矩阵相乘 2022年度总结 CUDA初学笔记 汇编语言之保护模式 汇编语言之实模式 浅析 libc++ 中的 string 实现 std::expected 基本使用 CSAPP第九章笔记之虚拟内存 CSAPP第八章笔记之异常控制流 2021年度总结 编译安装GCC12 如何给LLVM贡献代码 CSAPP第三章笔记Part 2 CSAPP第三章笔记Part 1 GDB基本使用笔记 读Effetive Modern C++ 之类型推导 给计算机新生的一封信 CMake学习笔记 由Redis学习数据结构--字典 C++中lambda表达式基础 由Redis学习数据结构--链表 对Python及爬虫行业的思考 Vim实用技巧 浅谈C++中的类 简要剖析const关键字 正则表达式基础总结 使用Hugo和Firebase部署个人博客 浅谈HTTPS证书 Linux常用命令总结 Git学习笔记 Linux下使用v2ray 娱乐至死读书笔记 自控力读书笔记 少有人走的路力读书笔记 Postfix & Dovecot 自建邮箱服务 Linux用户管理 Linux开机流程 Linux文件与目录 Linux硬盘管理 Linux服务浅谈 About Me
C++模板基础
Jun · 2022-02-27 · via Jun's Blog

· Jun

函数模板

先看几个简单的例子了解下函数模板是如何使用的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
template<typename T>
T max(T a, T b)
{
  return b < a ? a : b;
}

max(1,2); // 隐式指定了模板实参为int,由编译器推导
max<int>(4,2); // 显式指定了模板实参为int

template<int I>
int add() {
    return I + 42;
}

add<42>();

在尖括号间是模板参数列表(template parameter list),其中元素称为模板参数(template parameter),而T 是类型参数,表示任意类型。

当我们调用了一个函数模板时,我们隐式或显式指定了模板实参(template argument),将其绑定到模板参数上。

模板类型参数

在上面的例子中的max函数中,有一个模板类型参数(type parameter)。我们可以将类型参数看作类型说明符,可以用它来指定返回类型或函数的参数类型。

类型推断中的类型转换

  • 如果调用参数是按引用传递的,任何类型都不被转换
  • 如果调用参数是按值传递的,那么只有退化(decay)这类转换是被允许的。

注意类型推断并不适用于默认调用参数,如:

1
2
3
4
5
6
7
8
template<typename T>
void f(T = ""){}

f(); // Error

template<typename T = std::string>
void g(T = ""){}
g() // OK

非类型参数

模板参数不一定要是某种具体的类型,也可以是常规数值。比如上面例子中的add函数。对于非类型参数,C++17后可以不用制定其类型而是用auto进行推导。 以下代码在C++17中也是成立的:

1
2
3
4
template<auto X>
auto echo(){
    return X;
}

函数模板的重载

  • 如果有一个非模板函数和一个与其同名的函数模板共存,并且这个模板可以被实例化为与非模板函数具有相同类型的调用参数,在其他因素完全相同的情况下,优先选择非模板函数。
  • 如果模板可以实例化出一个更匹配的函数,选择此模板。
  • 可以显式指定一个空的模板列表,这表明它会被解析成一个模板调用。

类模板

1
2
3
4
5
6
7
8
9
template<typename T>
class Stack {
  std::vector<T> elems;
public:
  void push(const T& elem);
  const T& top() const;
};

Stack<int> IntStack;

与函数模板不同,编译器不可以隐式推导出模板参数类型,所以在使用类模板时,我们必须要在模板名后的尖括号内显式提供模板实参。在类模板内部,T可以像普通类型一样被用来声明成员变量和成员函数。在类内部,Stack就表示这个类,但在外部使用时,必须写Stack<T>

成员模板

无论是普通类还是模板类都可以包含本身是模板的成员函数。这种成员被称为成员模板(member template)。

对于类模板,类和成员各自有自己独立的模板参数。而为了实例化一个类模板的成员模板,我们必须同时提供类和函数模板的实参。

.template

有时,在调用一个成员模板的时候,有必要显式地指出模板参数。此时,必须使用.template来告诉编译器<后面是一个模板参数列表。具体可以看下面这个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <bitset>
#include <iostream>

template <unsigned long N>
void printBitset(std::bitset<N> const& bs) {
    std::cout << bs.template to_string<char, std::char_traits<char>,
				       std::allocator<char>>();
}

int main()
{
	std::bitset<5> bit;
	printBitset(bit);
}

模板类的全特化

为了特化一个模板,在类模板声明的前面改为template<>,并且需要指明所需要特化的类型。这些用于特化类模板的类型被用作模板参数,并且需要紧跟在类名的后面:

1
2
3
4
template<>
class Stack<std::string>{
...
};

所有其它出现T的地方,都应该被特化为类模板的类型。

偏特化

类模板可以被部分特化,这样就可以为某些其它特殊情况提供特殊的实现。比如:

1
2
3
4
5
6
template<typename T>
class Stack<T*> {
  std::vector<T*> elem;
public:
  T* top const;
};

注意特化后的函数接口可能不同,比如top()

类型别名

类型别名有两种形式:

  • typedef Stack<int> IntStack
  • using IntStack = Stack<int>

这一过程称为*alias declaration。 在这种情况下我们都只是为一个已存在的类型定义了一个别名,并没有定义新的类型。新的名字称为type alias*。

别名模板

*alias declaration*也可以被模板化,这样就可以给一组类型取一个方便的名字。

1
2
template<typename T>
using DequeStack = Stack<T, std::deque<T>>

类成员的别名模板

1
2
3
4
5
6
7
template<typename T>
struct S {
  using Ty = T;
};

template<typename T>
using MyT = typename S<T>::Ty;

注意在使用S<T>::Ty是必须在前面加上typename,向编译器表明这是一个类型,否则编译器默认认为只是一个普通成员变量。

变参模板

从C++11开始,模板可以接受一组数量可变的参数,这种模板被称为变参模板(variadic template)

1
2
3
4
5
6
template<typename T, typename... Args>
void print(T arg1, Args... args) {
    std::cout << arg1 << "\n";
    if constexpr (sizeof...(Args) > 0) // 编译期if
        print(args...);
}

变参下标

1
2
3
4
5
6
7
8
9
template<auto... Idx, typename C>
void printRange(const C& coll) {
    print(coll[idx]...);
}

int main() {
    std::vector<int> vec {1,2,3,4,5,6};
    printRange<0,2,3>(vec); // => print(vec[0], vec[2], vec[3])
}