Skip to content

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++ 形参包展开的语法只有三个点,那么我们就按照已知的去思考。假设我们传入了 voidint 类型,带入进去。Ret 就是 voidArgs... 展开就是 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...... 。其实注释也说的非常简单直观,不过我们还是要带入理解一下。