C++ 形参包展开有 6 个点?
前言
我们知道 C++ 要想使用可变参数,需要使用到模板中的形参包,而想要获取形参包中的元素,则要进行包展开。
基本包展开
cpp
template<typename... Args> // 类型形参包
void f(Args...args){ // 函数形参包
// 创造合适的包展开场所(花括号包围的初始化器)
int _[]{ (std::cout << args << ' ',0)... };
std::cout << '\n';
// C++17 折叠表达式展开
((std::cout << args << ' '), ...);
}
运行测试。
如你所见,这些都是使用了三个点 ...
进行展开。的确,包展开的语法就是三个点。
6 个点 Args......
的特殊情况
我之所以会说有 6 个点的情况,那是因为在某些特殊情况下会叠加使用:
cpp
template<class Ret, class... Args>
void f(Ret(*func)(Args......)) {
func(1); // 调用函数指针
}
运行测试。
如你所见,这里的 Args......
出现了 6 个点。其实不用感到奇怪,记住我们以往教程说的话:
- C++ 的类型就和拼图一样。
既然 C++ 形参包展开的语法只有三个点,那么我们就按照已知的去思考。假设我们传入了 void
、int
类型,带入进去。Ret
就是 void
,Args...
展开就是 int
,加上没有用到的那 ...
那么组合起来是?
cpp
void(*func)(int...)
发现了吗?C 语言变长实参罢了,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
的文档中,提供了这个库的一个平凡实现(虽然目前没有标准库是这样实现的),里面出现了:
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......
。其实注释也说的非常简单直观,不过我们还是要带入理解一下。