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引入的新特性,它允许在编译时期对参数包进行高效的展开和计算。折叠表达式可以在模板元编程、递归算法和容器操作等方面简化代码,提高性能,并提供更简洁的语法。

折叠表达式主要有两种形式:一元折叠和二元折叠。

一元折叠:

  1. 一元左折叠 ((init op ...)): 将操作符应用于参数包中的元素,从左到右依次执行。例如,(a, b, c, d) + ... 将展开为 (((a + b) + c) + d)
  2. 一元右折叠 ((... op init)): 将操作符应用于参数包中的元素,从右到左依次执行。例如,(a, b, c, d) + ... 将展开为 (d + (c + (b + a)))

二元折叠:

  1. 二元左折叠 ((init op ... op last)): 将操作符应用于参数包中的元素,最后一个元素与初始值结合。例如,(init op a, b, c, d) 将展开为 ((init op a) op (b op (c op d)))
  2. 二元右折叠 ((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及后续版本引入了多种类型推导机制,主要包括:

  1. auto关键字

    • 自C++11起,auto关键字可用于局部变量声明,编译器会根据初始化表达式自动推导变量类型。例如:

      auto x = 42; // x 的类型被推导为 int
      auto y = std::make_unique<MyClass>(); // y 的类型被推导为 std::unique_ptr<MyClass>
      
  2. decltype关键字

    • decltype用于推导表达式的类型,它可以返回表达式在编译时的静态类型,包括顶层const、引用等修饰符。

      int i = 0;
      decltype(i) j; // j 的类型被推导为 int
      decltype((i)) k; // k 的类型被推导为 int&
      
  3. 模板类型推导

    • 在模板函数和模板类中,编译器可以根据实参推导模板参数类型。

      template<typename T>
      void f(T t) {} // T 的类型在调用 f 时根据传入的实参推导
      
  4. 折叠表达式中的类型推导

    • C++17引入了折叠表达式,编译器能够根据表达式折叠规则推导出最终的类型。

      template<typename... Args>
      auto sum(Args... args) {
          return (... + args); // 类型推导会基于+操作的结果类型
      }
      
  5. std::result_of(C++11)和std::invoke_result(C++17)

    • 这些类型特质可以帮助推导可调用对象(函数、函数指针、成员函数指针、lambda表达式等)在给定参数类型时的返回类型。

ThreadPool::enqueue方法的代码片段中,std::result_of用于推导传入的可调用对象在给定参数类型时的返回类型,以便创建与任务结果类型相匹配的std::future


相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-03-18 21:36:03       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-18 21:36:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-18 21:36:03       20 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-18 21:36:03       20 阅读

热门阅读

  1. 七.pandas处理第三方数据

    2024-03-18 21:36:03       20 阅读
  2. react中点击按钮不能获取最新的state时候

    2024-03-18 21:36:03       28 阅读
  3. 大模型学习中,为什么要把AI当人看?

    2024-03-18 21:36:03       25 阅读
  4. server.servlet.path和#server.servlet.context-path的区别

    2024-03-18 21:36:03       24 阅读
  5. 【C#语言入门】19. 什么是类

    2024-03-18 21:36:03       21 阅读
  6. 使用TOPDN做L53免费域名DNS解析方法

    2024-03-18 21:36:03       23 阅读
  7. Rust矢量数据库领域的优势

    2024-03-18 21:36:03       21 阅读
  8. 【Python学习笔记】Python logging模块的学习

    2024-03-18 21:36:03       25 阅读
  9. 算法笔记p142快速排序

    2024-03-18 21:36:03       22 阅读
  10. docker服务起不来原因及解决

    2024-03-18 21:36:03       21 阅读