see #2: move non-type template param auto to ch02

This commit is contained in:
Changkun Ou
2019-07-11 11:07:10 +02:00
parent b073e290fb
commit dd8cf90bbe
10 changed files with 149 additions and 180 deletions

View File

@@ -8,7 +8,7 @@ order: 1
I would love if you support me to make the book better:
[![](https://img.shields.io/badge/donate-PayPal-104098.svg?style=popout-square&logo=PayPal)](https://www.paypal.me/ouchangkun/4.99eur)
[![](https://img.shields.io/badge/donate-PayPal-104098.svg?style=popout-square&logo=PayPal)](https://www.paypal.me/changkunde/4.99eur)
## 资助

View File

@@ -651,7 +651,7 @@ class Magic<int,
std::vector<int>>> darkMagic;
```
既然是任意形式,所以个数为0的模板参数也是可以的:`class Magic<> nothing;`
既然是任意形式,所以个数为 0 的模板参数也是可以的:`class Magic<> nothing;`
如果不希望产生的模板参数个数为0可以手动的定义至少一个模板参数
@@ -670,8 +670,8 @@ template<typename... Args> void printf(const std::string &str, Args... args);
首先,我们可以使用 `sizeof...` 来计算参数的个数,:
```cpp
template<typename... Args>
void magic(Args... args) {
template<typename... Ts>
void magic(Ts... args) {
std::cout << sizeof...(args) << std::endl;
}
```
@@ -692,20 +692,22 @@ magic(1, ""); // 输出2
```cpp
#include <iostream>
template<typename T0>
void printf(T0 value) {
void printf1(T0 value) {
std::cout << value << std::endl;
}
template<typename T, typename... Args>
void printf(T value, Args... args) {
template<typename T, typename... Ts>
void printf1(T value, Ts... args) {
std::cout << value << std::endl;
printf(args...);
printf1(args...);
}
int main() {
printf(1, 2, "123", 1.1);
return 0;
printf1(1, 2, "123", 1.1);
return 0;
}
```
**2. 变参模板展开**
你应该感受到了这很繁琐,在 C++17 中增加了变参模板展开的支持,于是你可以在一个函数中完成 `printf` 的编写:
```cpp
@@ -716,39 +718,8 @@ void printf(T0 t0, T... t) {
}
```
**2. 初始化列表展开**
> 这个方法需要之后介绍的知识,读者可以简单阅读一下,将这个代码段保存,在后面的内容了解过了之后再回过头来阅读此处方法会大有收获。
递归模板函数是一种标准的做法,但缺点显而易见的在于必须定义一个终止递归的函数。
这里介绍一种使用初始化列表展开的黑魔法:
```cpp
// 编译这个代码需要开启 -std=c++14
template<typename T, typename... Args>
auto print(T value, Args... args) {
std::cout << value << std::endl;
return std::initializer_list<T>{([&] {
std::cout << args << std::endl;
}(), value)...};
}
int main() {
print(1, 2.1, "123");
return 0;
}
```
在这个代码中,额外使用了 C++11 中提供的初始化列表以及 Lambda 表达式的特性(下一节中将提到),而 std::initializer_list 也是 C++11 新引入的容器(以后会介绍到)。
通过初始化列表,`(lambda 表达式, value)...` 将会被展开。由于逗号表达式的出现,首先会执行前面的 lambda 表达式,完成参数的输出。唯一不美观的地方在于如果不使用 `return` 编译器会给出未使用的变量作为警告。
> 事实上,有时候我们虽然使用了变参模板,却不一定需要对参数做逐个遍历,我们可以利用 `std::bind` 及完美转发等特性实现对函数和参数的绑定,从而达到成功调用的目的。
> 关于这方面的使用技巧请参考习题TODO
### 折叠表达式
C++ 17 中将变长参数这种特性进一步带给了表达式,考虑下面这个例子:
@@ -764,6 +735,51 @@ int main() {
}
```
### 非类型模板参数推导
前面我们主要提及的是模板参数的一种形式:类型模板参数。
```cpp
template <typename T, typename U>
auto add(T t, U u) {
return t+u;
}
```
其中模板的参数 `T``U` 为具体的类型。
但还有一种常见模板参数形式可以让不同字面量成为模板参数,即非类型模板参数:
```cpp
template <typename T, int BufSize>
class buffer_t {
public:
T& alloc();
void free(T& item);
private:
T data[BufSize];
}
buffer_t<int, 100> buf; // 100 作为模板参数
```
在这种模板参数形式下,我们可以将 `100` 作为模板的参数进行传递。
在 C++11 引入了类型推导这一特性后,我们会很自然的问,既然此处的模板参数
以具体的字面量进行传递,能否让编译器辅助我们进行类型推导,
通过使用占位符 `auto` 从而不再需要明确指明类型?
幸运的是C++17 引入了这一特性,我们的确可以 `auto` 关键字,让编译器辅助完成具体类型的推导,
例如:
```cpp
template <auto value> void foo() {
std::cout << value << std::endl;
return;
}
int main() {
foo<10>(); // value 被推导为 int 类型
}
```
## 2.6 面向对象
### 委托构造

View File

@@ -12,49 +12,9 @@ order: 10
本章对即将到来的 C++17 进行介绍几个月前2016 年),目前为止,还没有一个正式发布的编译器来编译 C++17 特性的代码,本节作为扩展主题,供对 C++ 的历史进程及其未来发展感兴趣的读者阅读。
## 10.1 主要入选特性
### 非类型模板参数的 auto
模板参数分为两种,一种是类型模板参数,也是我们用得最多的一种:
```cpp
template <typename T, typename U>
auto add(T t, U u) {
return t+u;
}
```
里面的 `T``U` 都是类型模板参数。另一种是非类型模板参数,它可以让不同的字面量成为模板的参数:
```cpp
template <typename T, int BufSize>
class buffer_t {
public:
T& alloc();
void free(T& item);
private:
T data[BufSize];
}
buffer_t<int, 100> buf; // 100 作为模板参数
```
遗憾的是我们在编写模板的时候就必须明确非类型模板参数的具体类型C++17 打破了这一限制,让我们能够在非类型模板参数中使用 `auto` 关键字,从而让编译器推导具体的类型:
```cpp
template <auto value> void foo() {
return;
}
foo<10>(); // value 被推导为 int 类型
```
## 10.2 未入选特性
C++ 组委会在讨论投票最终确定 C++17 有很多提案,诸如 **Concepts**/**Ranges**/**Module** 等等,其中最受关注的就是 **Concepts**,可惜这一提案最终被拒,作为技术规范(Technical Specifications, TS) 将其发布。
### Concepts TS
## Concepts TS
**Concepts** 是对 C++ 模板编程的进一步增强扩展。简单来说,**Concepts** 是一种编译期的特性,它能够让编译器在编译期时对模板参数进行判断,从而大幅度增强我们在 C++ 中模板编程的体验。使用模板进行编程时候我们经常会遇到各种令人发指的错误,这是因为到目前为止我们始终不能够对模板参数进行检查与限制,例如下面简单的两行代码会造成大量的几乎不可读的编译错误:

View File

@@ -19,5 +19,5 @@ template<bool T> class MagicType {
int main() {
// the >> in template
std::vector<std::vector<int>> matrix;
std::vector<MagicType<(1>2)>> magic;
std::vector<MagicType<(1>2)>> magic; // legal, but not recommended
}

View File

@@ -0,0 +1,18 @@
//
// 2.4.default.template.param.cpp
// chapter 2 language usability
// modern cpp tutorial
//
// created by changkun at changkun.de
//
#include <iostream>
template<typename T = int, typename U = int>
auto add(T x, U y) -> decltype(x+y) {
return x+y;
}
int main() {
std::cout << add(1, 2) << std::endl;
}

View File

@@ -0,0 +1,46 @@
//
// 2.15.variadic.template.param.cpp
// chapter 2 language usability
// modern cpp tutorial
//
// created by changkun at changkun.de
//
#include <iostream>
#include <vector>
#include <string>
// sizeof...
template<typename... Ts>
void magic(Ts... args) {
std::cout << sizeof...(args) << std::endl;
}
// 1 recursive parameter unpack
template<typename T0>
void printf1(T0 value) {
std::cout << value << std::endl;
}
template<typename T, typename... Ts>
void printf1(T value, Ts... args) {
std::cout << value << std::endl;
printf1(args...);
}
// 2 variadic template parameter unfold
template<typename T0, typename... T>
void printf2(T0 t0, T... t) {
std::cout << t0 << std::endl;
if constexpr (sizeof...(t) > 0) printf2(t...);
}
int main() {
magic();
magic(1);
magic(1,"");
printf1(1, 2, "123", 1.1);
printf2(1, 2.3, "abc");
return 0;
}

View File

@@ -1,3 +1,11 @@
//
// 2.16.fold.expression.cpp
// chapter 2 language usability
// modern cpp tutorial
//
// created by changkun at changkun.de
//
#include <iostream>
template<typename ... T>
auto sum(T ... t) {

View File

@@ -0,0 +1,18 @@
//
// 2.18.non.type.template.auto.cpp
// chapter 2 language usability
// modern cpp tutorial
//
// created by changkun at changkun.de
//
#include <iostream>
template <auto value> void foo() {
std::cout << value << std::endl;
return;
}
int main() {
foo<10>(); // value is deduced as type int
}

View File

@@ -1,76 +0,0 @@
//
// 2.6.cpp
// modern c++ tutorial
//
// created by changkun at changkun.de
//
// 模板增强
#include <iostream>
#include <vector>
#include <string>
template class std::vector<bool>; // 强行实例化
extern template class std::vector<double>; // 不在该编译文件中实例化模板
template< typename T, typename U, int value>
class SuckType {
public:
T a;
U b;
SuckType():a(value),b(value){}
};
// template< typename T>
// typedef SuckType<std::vector<int>, T, 1> NewType; // 不合法
template <typename T>
using NewType = SuckType<int, T, 1>; // 合法
// 默认模板类型
template<typename T = int, typename U = int>
auto add(T x, U y) -> decltype(x+y) {
return x+y;
}
// sizeof...
template<typename... Args>
void magic(Args... args) {
std::cout << sizeof...(args) << std::endl;
}
// 1. 递归解参数包
template<typename T>
void printf1(T value) {
std::cout << value << std::endl;
}
template<typename T, typename... Args>
void printf1(T value, Args... args) {
std::cout << value << std::endl;
printf1(args...);
}
// 2.初始化列表展开解参数包
template<typename T, typename... Args>
auto printf2(T value, Args... args) {
std::cout << value << std::endl;
return std::initializer_list<T>{([&] {
std::cout << args << std::endl;
}(), value)...};
}
int main() {
std::vector<std::vector<int>> wow; // 注意尖括号
NewType<int> t;
magic();
magic(1);
magic(1,"");
printf1(1, 2.3, "abc");
printf2(1, 2.3, "abc");
return 0;
}

View File

@@ -1,21 +0,0 @@
#include <iostream>
template<typename T0>
void printf(T0 value) {
std::cout << value << std::endl;
}
template<typename T, typename... Args>
void printf(T value, Args... args) {
std::cout << value << std::endl;
printf(args...);
}
template<typename T0, typename... T>
void printf_short(T0 t0, T... t) {
std::cout << t0 << std::endl;
if constexpr (sizeof...(t) > 0) printf(t...);
}
int main() {
printf_short(1, 2, "123", 1.1);
return 0;
}