From 0574c2ffbf4702aa37e2cd9b5ee974c6dd3c9bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Tue, 28 May 2024 01:37:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8D=A2=E7=91=9F=E6=97=A5?= =?UTF-8?q?=E7=BB=8F=EF=BC=8C=E5=BD=A2=E5=8F=82=E5=8C=85=E5=B1=95=E5=BC=80?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../README.md" | 1 + ...16\344\270\252\347\202\271\357\274\237.md" | 93 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 "src/\345\215\242\347\221\237\346\227\245\347\273\217/\345\275\242\345\217\202\345\214\205\345\261\225\345\274\200\346\234\2116\344\270\252\347\202\271\357\274\237.md" diff --git "a/src/\345\215\242\347\221\237\346\227\245\347\273\217/README.md" "b/src/\345\215\242\347\221\237\346\227\245\347\273\217/README.md" index 383fd68f..5109c25e 100644 --- "a/src/\345\215\242\347\221\237\346\227\245\347\273\217/README.md" +++ "b/src/\345\215\242\347\221\237\346\227\245\347\273\217/README.md" @@ -16,3 +16,4 @@ - [如何阅读STL源码?](如何阅读STL源码?.md) - [godbolt使用文档](godbolt使用文档.md) - [STL中类的字节大小](STL中类的字节大小.md) +- [形参包展开有6个点?](形参包展开有6个点?.md) diff --git "a/src/\345\215\242\347\221\237\346\227\245\347\273\217/\345\275\242\345\217\202\345\214\205\345\261\225\345\274\200\346\234\2116\344\270\252\347\202\271\357\274\237.md" "b/src/\345\215\242\347\221\237\346\227\245\347\273\217/\345\275\242\345\217\202\345\214\205\345\261\225\345\274\200\346\234\2116\344\270\252\347\202\271\357\274\237.md" new file mode 100644 index 00000000..b697c4ad --- /dev/null +++ "b/src/\345\215\242\347\221\237\346\227\245\347\273\217/\345\275\242\345\217\202\345\214\205\345\261\225\345\274\200\346\234\2116\344\270\252\347\202\271\357\274\237.md" @@ -0,0 +1,93 @@ + +# C++ 形参包展开有 6 个点? + +## 前言 + +我们知道 C++ 要想使用可变参数,需要使用到模板中的形参包,而想要获取形参包中的元素,则要进行包展开。 + +## 基本包展开 + +```cpp +template // 类型形参包 +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 +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 +void f(Ret(*func)(Args...,...)); +``` + +中间加了一个逗号变成了 `...,...`,这也是可以的的。 + +完整代码: + +```cpp +template +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 +struct is_function : std::true_type {}; + +// 对变参数函数,如 std::printf 的特化 +template +struct is_function : std::true_type {}; +``` + +如上所示,这里就有 `Args......` 。其实注释也说的非常简单直观,不过我们还是要带入理解一下。