C++不定参数模板、折叠表达式和类型推导
不定参数模板
文章目录
- 不定参数模板
- 可变参数模板
- 折叠表达式
- 函数返回类型后置语法
- std::result_of(C++11) && std::invoke_result(C++17)
- 关于C++类型推导
可变参数模板
模板函数可以接受不定参数数目,这通常通过可变参数模板来实现。可变参数模板允许你在模板声明中使用省略号(…)来表示任意数量的模板参数
template<typename... Args>
auto sum(Args... args) {
return (args + ...);
}
int main() {
std::cout << sum(1, 2, 3, 4, 5) << std::endl;
return 0;
}
折叠表达式
折叠表达式是C++17引入的新特性,它允许在编译时期对参数包进行高效的展开和计算。折叠表达式可以在模板元编程、递归算法和容器操作等方面简化代码,提高性能,并提供更简洁的语法。
折叠表达式主要有两种形式:一元折叠和二元折叠。
一元折叠:
- 一元左折叠 (
(init op ...)
): 将操作符应用于参数包中的元素,从左到右依次执行。例如,(a, b, c, d) + ...
将展开为(((a + b) + c) + d)
。 - 一元右折叠 (
(... op init)
): 将操作符应用于参数包中的元素,从右到左依次执行。例如,(a, b, c, d) + ...
将展开为(d + (c + (b + a)))
。
二元折叠:
- 二元左折叠 (
(init op ... op last)
): 将操作符应用于参数包中的元素,最后一个元素与初始值结合。例如,(init op a, b, c, d)
将展开为((init op a) op (b op (c op d)))
。 - 二元右折叠 (
(first op ... op init)
): 类似于二元左折叠,但方向是从右到左。例如,(a op b, c, d op init)
将展开为(a op (b op (c op (d op init))))
。
折叠表达式支持多种操作符,包括算术运算符(如+
, -
, *
, /
, %
等)、位运算符、逻辑运算符(如&&
, ||
)、逗号运算符 ,
以及赋值运算符等。
在实践中,折叠表达式可用于计算参数包的和、乘积、逻辑“与”、“或”操作,或者合并字符串等场景
应用:
- 左折叠
template<typename... Args>
auto f(Args... args) {
return (args - ...);
}
cout << f(7 , 9 , 6) << endl;
//(7 - (9 - 6)) = 4
- 右折叠
template<typename... Args>
auto f(Args... args) {
return (... - args);
}
cout << f(7 , 9 , 6) << endl;
//((7 - 9) - 6) = -8
- 求和
template<typename... Args>
auto sum(Args... args) {
return (args + ...);
}
- 模板函数乘后求和
template<typename First , typename... Args>
auto f(First first , Args... args) {
return (first * (... + args));
}
输入必须大一一个参数,将第一个参数和后面的参数中的所有参数相加后的结果相乘
- 字符串拼接
template<typename... Args>
auto f(Args&&... args) {
return (std::string{} + ... + args);
}
函数返回类型后置语法
函数返回类型后置语法是C++11引入的新特性,它允许我们在函数声明或定义时将返回类型放在函数名后面,通过->符号分隔。这种语法有时被称为后置返回类型,特别适用于那些需要使用模板或 decltype 来推导返回类型的情况。
template <typename T>
auto fun(T x) -> decltype(x * x) {
return x * x;
}
- lambda表达式
auto f = [](int x) -> int{
return x + 1;
};
std::result_of(C++11) && std::invoke_result(C++17)
std::result_of
是C++11中引入的一个标准库特性,它是一个类型 traits 类,用于推断给定函数对象在给定参数类型下调用后的返回类型。然而,从C++17开始,std::result_of
已被std::invoke_result
替代,因为在C++17中引入了更灵活的std::invoke
函数,它可以处理更多种类的可调用对象。
在C++11/14中,std::result_of
的基本用法如下:
template <typename Fn, typename... Args>
struct result_of<Fn(Args...)>; // 推导函数类型
// 使用示例
std::result_of<decltype(f)*(*)(int)>::type result_type; // 推导函数f接受int参数的返回类型
但在C++17及更高版本中,建议改用std::invoke_result
:
template <typename Fn, typename... Args>
using invoke_result = typename std::invoke_result_t<Fn, Args...>;
// 使用示例
using result_type = std::invoke_result_t<decltype(f), int>; // 推导函数f接受int参数的返回类型
回到上述ThreadPool::enqueue
函数的代码片段,std::result_of
用于推导传入的可调用对象f
在给定参数Args
时调用的返回类型,这样可以正确地声明std::future
所持有的类型。
关于C++类型推导
C++类型推导指的是编译器在编译期间自动确定变量或表达式的类型的能力。C++11及后续版本引入了多种类型推导机制,主要包括:
-
auto
关键字:-
自C++11起,
auto
关键字可用于局部变量声明,编译器会根据初始化表达式自动推导变量类型。例如:auto x = 42; // x 的类型被推导为 int auto y = std::make_unique<MyClass>(); // y 的类型被推导为 std::unique_ptr<MyClass>
-
-
decltype
关键字:-
decltype
用于推导表达式的类型,它可以返回表达式在编译时的静态类型,包括顶层const、引用等修饰符。int i = 0; decltype(i) j; // j 的类型被推导为 int decltype((i)) k; // k 的类型被推导为 int&
-
-
模板类型推导:
-
在模板函数和模板类中,编译器可以根据实参推导模板参数类型。
template<typename T> void f(T t) {} // T 的类型在调用 f 时根据传入的实参推导
-
-
折叠表达式中的类型推导:
-
C++17引入了折叠表达式,编译器能够根据表达式折叠规则推导出最终的类型。
template<typename... Args> auto sum(Args... args) { return (... + args); // 类型推导会基于+操作的结果类型 }
-
-
std::result_of
(C++11)和std::invoke_result
(C++17):- 这些类型特质可以帮助推导可调用对象(函数、函数指针、成员函数指针、lambda表达式等)在给定参数类型时的返回类型。
在ThreadPool::enqueue
方法的代码片段中,std::result_of
用于推导传入的可调用对象在给定参数类型时的返回类型,以便创建与任务结果类型相匹配的std::future
。