# 第九章 扩展主题：C++17 简介

## 一、本节内容

本节内容包括：

- 扩展主题: C++17 简介
+ 主要入选特性
+ 非类型模板参数的 `auto`
+ `std::variant<>`
+ 结构化绑定(Structured bindings)
+ 变量声明的强化
+ 未入选特性
+ Concepts

本节对即将到来的 C++17 进行介绍，几个月前（2016 年），目前为止，还没有一个正式发布的编译器来编译 C++17 特性的代码，本节作为扩展主题，供对 C++ 的历史进程及其未来发展感兴趣的读者阅读。

## 二、主要入选特性

### 非类型模板参数的 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 类型
```

### std::variant<>

熟悉 `boost` 的人应该很早就听说过 `variant<>` 了。`variant<>` 可以用于存储和操作不同类型的对象。我们在前面（[对标准库的扩充：新增容器](./4.containers.md)）对于迭代 `std::tuple` 时，简单使用了 `boost::variant<>`。提供给 `variant<>` 的类型模板参数可以让一个 `variant<>` 从而容纳提供的几种类型的变量（在其他语言(例如 Python/JavaScript 等)表现为动态类型）。

C++17 正式将 `variant<>` 纳入标准库，摇身一变成为 `std::variant<>`，有了它之后，我们可以将前面的代码更改为：

```cpp
#include <variant>
template <size_t n, typename... Args>
std::variant<Args...> _tuple_index(size_t i, const std::tuple<Args...>& tpl) {
if (i == n)
return std::get<n>(tpl);
else if (n == sizeof...(Args) - 1)
throw std::out_of_range("越界.");
else
return _tuple_index<(n < sizeof...(Args)-1 ? n+1 : 0)>(i, tpl);
}
template <typename... Args>
std::variant<Args...> tuple_index(size_t i, const std::tuple<Args...>& tpl) {
return _tuple_index<0>(i, tpl);
}
```

### 结构化绑定(Structured bindings)

结构化绑定提供了类似其他语言中提供的多返回值的功能。到目前为止，我们可以通过 `std::tuple` 来构造一个元组，囊括多个返回值。但缺陷是显而易见的，我们没有一种简单的方法直接从元组中拿到并定义元组中的元素，尽管我们可以使用 `std::tie` 对元组进行拆包，但我们依然必须非常清楚这个元组包含多少个对象，各个对象是什么类型。

C++17 给出的结构化绑定可以让我们写出这样的代码：

```cpp
std::tuple<int,double,std::string> f() {
return std::make_tuple(1,2.3,"456");
}
auto [x,y,z] = f(); // x,y,z 分别被推导为int,double,std::string
```

### 变量声明的强化

变量的声明在虽然能够位于任何位置，甚至于 `for` 语句内能够声明一个临时变量 `int`，但始终没有办法在 `if` 和 `switch` 语句中声明一个临时的变量。例如：

```cpp
auto p = map_container.try_emplace(key, value);
if(!p.second) {
//...
} else {
//...
}
```

C++17 消除了这一限制，使得我们可以：

```cpp
if (auto p = m.try_emplace(key, value); !p.second) {
//...
} else {
//...
}
```

## 三、未入选特性

C++ 组委会在讨论投票最终确定 C++17 有很多提案，诸如 **Concepts**/**Ranges**/**Module** 等等，其中最受关注的就是 **Concepts**，可惜这一提案最终被拒，作为技术规范(Technical Specifications, TS) 将其发布。

### Concepts TS

**Concepts** 是对 C++ 模板编程的进一步增强扩展。简单来说，**Concepts** 是一种编译期的特性，它能够让编译器在编译期时对模板参数进行判断，从而大幅度增强我们在 C++ 中模板编程的体验。使用模板进行编程时候我们经常会遇到各种令人发指的错误，这是因为到目前为止我们始终不能够对模板参数进行检查与限制，例如下面简单的两行代码会造成大量的几乎不可读的编译错误：

```cpp
#include <list>
#include <algorithm>
int main() {
std::list<int> l = {1, 2, 3};
std::sort(l.begin(), l.end());
return 0;
}
```

而这段代码出现错误的根本原因在于，`std::sort` 对排序容器必须提供随机迭代器，否则就不能使用，而我们知道 `std::list` 是不支持随机访问的。用 **Concepts** 的话来说就是：`std::list`中的迭代器不满足`std::sort`中随机迭代器这个 **Concepts**（概念） 的 **requirements**（要求）。有了 **Concepts**，我们就可以这样：

```cpp
template <typename T>
requires Sortable<T> // Sortable 是一个 concept
void sort(T& c);
```

缩写为：

```cpp
template<Sortable T> // T 是一个 Sortable 的类型名
void sort(T& c)
```

甚至于直接将其作为类型来使用：

```cpp
void sort(Sortable& c); // c 是一个 Sortable 类型的对象
```

遗憾的是，C++组委会没有将 **Concetps** 纳入新标准，而是将其作为TS正式发布（其实早在 C++11 最终定案之前就已经有 **Concepts** 的呼声了，但 Concepts TS 是2015年才完整正式发布），也就是我们现在看到的 Concepts TS。C++组委会拒绝将 Concepts 纳入新标准的原因其实很简单，并不是技术层面上的原因，纯粹是觉得它还不够成熟。

Concepts TS 的发布到最后一次 C++17 的讨论会只相隔了不到四个月的时间，**Concepts** 的（唯一）实现只存在于一个未发布的 gcc 版本中。而 gcc 中关于 **Concepts** 的实现就是由撰写 Concepts TS 的人开发的，虽然它能够进行相关测试，但还没有认真讨论过这份 TS 会产生哪些不良后果，更何况这份 TS 都没有被测试过。此外，已知的 **Concepts** 的一个明显的作用就是去辅助实现 Ranges TS 等提案，但实际上它们也没有被选入 C++17，所以可以把 **Concepts** 继续延后。

## 总结

总的来说，类似于 Concepts/Ranges/Modules 这些令人兴奋的特性并没有入选至 C++17，这注定了 C++17 某种意义上来说相较于 C++11/14 依然只是小幅度更新，但我们有望在 C++2x 中看到这些东西的出现，这些内容对于一门已经三十多岁『高龄』的编程语言，依然是充满魅力的。

## 进一步阅读的参考资料

1. [Final features of C++17](https://meetingcpp.com/index.php/br/items/final-features-of-c17.html)
2. [C++17: will it be great or just ok?](https://codeplay.com/public/uploaded/filehost/0cbdaf_c++17post-oulu2016.pdf)
3. [Why Concepts didn't make C++17？](http://honermann.net/blog/2016/03/06/why-concepts-didnt-make-cxx17/)
4. [C++11/14/17 编译器支持情况](http://en.cppreference.com/w/cpp/compiler_support)

## 许可

<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-nd/4.0/80x15.png" /></a>

本教程由[欧长坤](https://github.com/changkun)撰写，采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源，参见[许可](../LICENSE)。