-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
94 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
|
||
# C++ 形参包展开有 6 个点? | ||
|
||
## 前言 | ||
|
||
我们知道 C++ 要想使用可变参数,需要使用到模板中的形参包,而想要获取形参包中的元素,则要进行包展开。 | ||
|
||
## 基本包展开 | ||
|
||
```cpp | ||
template<typename... Args> // 类型形参包 | ||
void f(Args...args){ // 函数形参包 | ||
// 创造合适的包展开场所(花括号包围的初始化器) | ||
int _[]{ (std::cout << args << ' ',0)... }; | ||
|
||
std::cout << '\n'; | ||
|
||
// C++17 折叠表达式展开 | ||
((std::cout << args << ' '), ...); | ||
} | ||
``` | ||
> [运行](https://godbolt.org/z/MW3KT56fn)测试。 | ||
如你所见,这些都是使用了三个点 `...` 进行展开。的确,**包展开的语法就是三个点**。 | ||
## 6 个点 `Args......` 的特殊情况 | ||
我之所以会说有 6 个点的情况,那是因为在某些特殊情况下会叠加使用: | ||
```cpp | ||
template<class Ret, class... Args> | ||
void f(Ret(*func)(Args......)) { | ||
func(1); // 调用函数指针 | ||
} | ||
``` | ||
|
||
> [运行](https://godbolt.org/z/vdseKb3f1)测试。 | ||
如你所见,这里的 **`Args......`** 出现了 6 个点。其实不用感到奇怪,记住我们以往教程说的话: | ||
|
||
- **C++ 的类型就和拼图一样。** | ||
|
||
既然 C++ 形参包展开的语法只有三个点,那么我们就按照已知的去思考。假设我们传入了 `void` 、`int` 类型,带入进去。`Ret` 就是 `void` ,`Args...` 展开就是 `int`,加上没有用到的那 `...` 那么组合起来是? | ||
|
||
```cpp | ||
void(*func)(int...) | ||
``` | ||
|
||
发现了吗?C 语言[**变长实参**](https://zh.cppreference.com/w/cpp/language/variadic_arguments)罢了,C++11 允许了变长实参的三个点可以不以逗号分隔。当然我们也可以继续用逗号分隔,例如: | ||
|
||
```cpp | ||
template<class Ret, class... Args> | ||
void f(Ret(*func)(Args...,...)); | ||
``` | ||
中间加了一个逗号变成了 `...,...`,这也是可以的的。 | ||
完整代码: | ||
```cpp | ||
template<class Ret, class... Args> | ||
void f(Ret(*func)(Args......)) { // 加不加逗号都行 | ||
func(1); // 调用函数指针 | ||
} | ||
// 带可变参数的示例函数 | ||
void func(int a, ...) { // 加不加逗号都行 | ||
std::cout << "沙贝 C 变长实参函数" << a << std::endl; | ||
} | ||
int main() { | ||
f(func); | ||
} | ||
``` | ||
|
||
## 总结 | ||
|
||
如你所见,这很简单。开始不理解时,可以先用已有的知识带入思考。 | ||
|
||
这个问题最初是一个粉丝提出来。,在 [`std::is_function`](https://zh.cppreference.com/w/cpp/types/is_function) 的文档中,提供了这个库的一个平凡实现(虽然目前没有标准库是这样实现的),里面出现了: | ||
|
||
```cpp | ||
// 对常规函数的特化 | ||
template<class Ret, class... Args> | ||
struct is_function<Ret(Args...)> : std::true_type {}; | ||
|
||
// 对变参数函数,如 std::printf 的特化 | ||
template<class Ret, class... Args> | ||
struct is_function<Ret(Args......)> : std::true_type {}; | ||
``` | ||
如上所示,这里就有 `Args......` 。其实注释也说的非常简单直观,不过我们还是要带入理解一下。 |