-
Notifications
You must be signed in to change notification settings - Fork 132
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
88 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,87 @@ | ||
|
||
# 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 个点的情况,那是特殊的,叠加在一起的: | ||
```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 {}; | ||
``` | ||
这样一段。其实注释也说的非常简单直观,不过我们还是要带入理解一下。 |