template<typename F, typename... Args> 是 C++11 引入的**可变参数模板(Variadic Templates)**声明。
它是现代 C++ 泛型编程的基石,允许函数或类接受任意数量 、任意类型 的参数。这行代码通常出现在实现通用工具(如 std::bind, std::make_shared, std::thread 构造函数等)的场景中。
1. 语法拆解
- **
typename F** :- 定义了一个普通的模板类型参数
F。 - 通常用来表示可调用对象 的类型(如函数指针、Lambda、仿函数、
std::function等)。
- 定义了一个普通的模板类型参数
- **
typename... Args** :- 定义了一个**模板参数包(Template Parameter Pack)**。
...(省略号)是关键,它表示Args不是一个单一类型,而是一系列类型的集合(例如int, double, std::string)。- 这个包可以包含 0 个、1 个或多个类型。
2. 核心用法:如何"解开"参数包?
定义了参数包后,你不能直接像使用普通类型那样使用 Args。你需要通过包展开(Pack Expansion) 来操作它。最常见的场景是配合**完美转发(Perfect Forwarding)**。
典型示例:实现一个简单的 invoke 函数
cpp
#include <iostream>
#include <utility> // for std::forward
// 1. 声明可变参数模板
template<typename F, typename... Args>
auto my_invoke(F&& f, Args&&... args) -> decltype(auto)
{
// 2. 包展开:将 args 包展开,并对每个参数应用 std::forward
// std::forward<Args>(args)...
// 如果 Args 是 <int, double>,args 是 (a, b)
// 展开后变成:std::forward<int>(a), std::forward<double>(b)
return std::forward<F>(f)(std::forward<Args>(args)...);
}
void print_sum(int a, int b) {
std::cout << "Sum: " << a + b << std::endl;
}
int main() {
// 调用普通函数
my_invoke(print_sum, 10, 20);
// 调用 Lambda
my_invoke([](const std::string& s) {
std::cout << "Hello, " << s << std::endl;
}, std::string("World"));
return 0;
}
3. 关键概念解析
A. 万能引用(Universal Reference / Forwarding Reference)
注意参数列表中的 F&& 和 Args&&...。
- 在模板中,
T&&并不一定代表右值引用。 - 如果传入的是左值,
T会被推导为T&,根据引用折叠规则,T& &&变成T&(左值引用)。 - 如果传入的是右值,
T被推导为T,T&&保持为右值引用。 - 目的 :这使得
my_invoke能够接收任何类型的参数(左值、右值、const、非 const),并保留其原始属性。
B. std::forward 的作用
std::forward<Args>(args)...确保参数在传递给内部函数f时,保持其原始的左值/右值属性。- 如果没有
std::forward,所有参数在函数内部都会变成左值,可能导致不必要的拷贝(例如无法触发移动语义)。
C. decltype(auto) (C++14)
- 用于自动推导返回类型。
- 它不仅能推导类型,还能保留引用限定符。如果
f返回一个引用,my_invoke也返回引用;如果返回值,它也返回值。
4. 常见应用场景
1、std::thread 的构造函数:
cpp
// 允许你传递任意函数和任意参数给新线程
std::thread t(my_function, arg1, arg2, arg3);
2、**std::make_shared / std::make_unique**
cpp
// 允许你在创建智能指针的同时,直接传递构造函数的参数
auto p = std::make_shared<MyClass>(1, "hello", 3.14);
3、日志系统:
cpp
template<typename... Args>
void log(const char* fmt, Args... args) {
printf(fmt, args...); // 展开参数包传给 printf
}
5. 总结
- **
typename F**:捕获可调用对象。 - **
typename... Args**:捕获任意数量的参数类型。 - **
Args&&... args**:通过万能引用接收任意类型的参数值。 - **
std::forward<Args>(args)...**:完美转发这些参数,保持其左值/右值属性,避免多余拷贝。
这是编写高性能、通用、类型安全的 C++ 库代码的标准模式。