首页 > 编程语言 >模板元编程与函数式

模板元编程与函数式

时间:2023-04-14 22:45:55浏览次数:51  
标签:std 函数 int 代码 编程 编译 参数 模板

参考:
【公开课】现代C++进阶:模板元编程与函数式
ppt和代码

在高性能计算中,一般使用函数式和元编程,而不使用面向对象。

简单的介绍:类型自动推导模板参数、模板特化

简单的实例:

#include <iostream>

template <class T>
T twice(T t) {
  return t * 2;
}

std::string twice(std::string t) { return t + t; }

// int main() {
//   std::cout << twice<int>(21) << std::endl;
//   std::cout << twice<float>(3.14f) << std::endl;
//   std::cout << twice<double>(2.718) << std::endl;
//   std::cout << twice<std::string>("hello") << std::endl;
// }

// 自动推导模板参数
int main() {
  std::cout << twice(21) << std::endl;
  std::cout << twice(3.14f) << std::endl;
  std::cout << twice(2.718) << std::endl;
  std::cout << twice(std::string("hello")) << std::endl;

  // twice("hello")不会使用twice(std::string t),而是使用twice(std::string t),从而导致错误。
  // std::cout << twice("hello") << std::endl;
}
  • 没有指定模板函数的类型时,会自动推导模板参数。
    【注】当我们使用特化重载的时候,需要注意代码调用的是否为特化函数,如代码中的 twice("hello")不会使用twice(std::string t),而是使用twice(std::string t),从而导致错误。这个问题可以使用SFINAE解决。
  • 使用template <class T>template <typename T>是完全等价的。

参数部分特化:

#include <iostream>
#include <vector>

template <class T>
T sum(std::vector<T> const &arr) { // 这里用了 const & 避免不必要的的拷贝。
  T res = 0;
  for (int i = 0; i < arr.size(); i++) {
    res += arr[i];
  }
  return res;
}

int main() {
  std::vector<int> a = {4, 3, 2, 1};
  // std::cout << sum({4, 3, 2, 1}) << std::endl;  // 错误,部分特化也不支持隐式转换。
  std::cout << sum(a) << std::endl;
  std::vector<float> b = {3.14f, 2.718f};
  std::cout << sum(b) << std::endl;
}

func(vector<T> t) 这样则可以限定仅仅为 vector 类型的参数。

整数也可以作为参数

#include <iostream>

template <int N = 1, class T>  // 整数也可以作为模板参数
void show_times(T msg) {
    for (int i = 0; i < N; i++) {
        std::cout << msg << std::endl;
    }
}

int main() {
    show_times("one");
    show_times<3>(42);
    show_times<4>('%');
}

代码说明:

  • 整数也可以作为模板参数,不过模板参数只支持整数类型(包括 enum),浮点类型、指针类型,不能声明为模板参数。自定义类型也不可以。【问题】为什么要支持整数作为模板参数:因为是编译期常量
  • int N 和 class T 可以一起使用。你只需要指定其中一部分参数即可,会自动根据参数类型(T msg)、默认值(int N = 1),推断尖括号里没有指定的那些参数。

template <int N> void func();void func(int N);的区别:

  • template <int N> 传入的 N,是一个编译期常量,每个不同的 N,编译器都会单独生成一份代码,并且对代码进行优化(如删除掉没有必要的判断语句)。比如上述代码中show_times<0>()函数调用,编译器就可以自动优化为一个空函数。因此模板元编程对高性能编程很重要。

  • func(int N),则变成运行期常量,编译器无法自动优化,只能运行时根据被调用参数 N 的不同。

模板的内部实现需要被暴露出来,除非使用特殊的手段,否则,声明和定义都必须放在头文件里,即不能分离声明和定义。但也正因如此,如果过度使用模板,会导致生成的二进制文件大小剧增,编译变得很慢等。

编译期常量

参考:C++干货系列——从编译期常量谈到constexpr(一)

总有些东西是编译器要求编译期间就要确定的,除了变量的类型外,最频繁出现的地方就是数组、switch的case标签和模板了。

数组中的编译期常量: int someArray[520];,有些时候我们不用显示得指明数组的大小,我们用字符串或花括号来初始化数组的时候,编译器会实现帮我们数好这个数组的大小,如:

int someArray[] = {5, 2, 0};
char charArray[] = "Ich liebe dich.";

模板中的编译期常量:除了类型以外,数字也可以作为模板的参数。这些数值变量包括int,long,short,bool,char和弱枚举enum等。如:

enum Color {RED, GREEN, BLUE};
 ​
template<unsigned long N, char ID, Color C>
struct someStruct {};
 ​
someStruct<42ul, 'e', GREEN> theStruct;

switch语句的分支判断也必须是编译期常量,和上边模板的情况非常类似。

 void comment(int phrase) {
   switch(phrase) {
   case 42:
   std::cout << "You are right!" << std::endl;
   break;
   case BLUE:
   std::cout << "Don't be upset!" << std::endl;
   break;
   case 'z':
   std::cout << "You are the last one!" << std::endl;
   break;
   default:
   std::cout << "This is beyond what I can handle..." << std::endl;
   }
 }

模板的应用:编译期优化案例

不进行优化前:

#include <iostream>

int sumto(int n, bool debug) {
    int res = 0;
    for (int i = 1; i <= n; i++) {
        res += i;
        if (debug)
            std::cout << i << "-th: " << res << std::endl;
    }
    return res;
}

int main() {
    std::cout << sumto(4, true) << std::endl;
    std::cout << sumto(4, false) << std::endl;
    return 0;
}

上述代码,我们声明了一个 sumto 函数,作用是求出从 1 到 n 所有数字的和。用一个 debug 参数控制是否输出调试信息。但是这样 debug 是运行时判断,这样即使是 debug 为 false 也会浪费 CPU 时间(始终需要执行判断语句,导致cpu浪费)。

我们当然可以使用ifdef来预编译阶段优化代码,这里我们演示使用模板函数来在编译器优化代码:

#include <iostream>

template <bool debug>
int sumto(int n) {
    int res = 0;
    for (int i = 1; i <= n; i++) {
        res += i;
        if constexpr (debug) // C++11的constexpr用于保证debug是编译期常量,如果constexpr中不是编译常量就会报错
            std::cout << i << "-th: " << res << std::endl;
    }
    return res;
}

int main() {
    std::cout << sumto<true>(4) << std::endl;
    std::cout << sumto<false>(4) << std::endl;
    return 0;
}

上述代码,

  • 生成多份代码:在编译阶段会根据sumto的被调用的情况生成相应的代码,如sumto<false>(4)就会将if语句和if语句下面的东西都删除;sumto<true>(4)就会将if语句删除,保留if语句下面的东西。也就是说上述代码在编译器会生成两份代码。
  • constexpr和模板尖括号内不能为编译常量:除了 if constexpr 的表达式不能用运行时变量,模板尖括号内的参数也不能。
  • 编译期 constexpr 的表达式,一般是无法调用其他函数的。但是如果能保证被调用函数里都可以在编译期求值,将他前面也标上 constexpr 即可。

SFINAE没看懂以后再说

http://kaiyuan.me/2018/05/08/sfinae/
https://en.cppreference.com/w/cpp/language/sfinae

标签:std,函数,int,代码,编程,编译,参数,模板
From: https://www.cnblogs.com/codingbigdog/p/17320164.html

相关文章

  • vue2源码-五、将模板编译解析成AST语法树1
    将模板编译成ast语法树complileToFunction方法vue数据渲染:template模板->ast语法树->render函数,模板编译的最终结果结果就是render函数。在complileToFunction方法中,生成render函数,需要以下两个核心步骤:通过parserHTML方法:将模板(template或html)内容编译成ast语法树通过co......
  • 编程一小时2023.4.14
    1.#include<bits/stdc++.h>usingnamespacestd;classnumber{intfz,fm;friendnumberoperator+(number&n1,number&n2);public:number(inta=0,intb=1){fz=a;fm=b;}friendintgcd(inta,intb);friendintmin_gb(number&n1......
  • 多元函数微积分期中复习复盘笔记
    多元微积分期中目录多元微积分期中极限与连续定义重极限与累次极限的关系例题多元函数的泰勒公式可微、全微分与偏导数向量值函数的可微性标量函数的可微性、全微分与偏导数雅各比矩阵可微与偏导数连续的关系例题方向导数与梯度方向导数梯度方向导数与梯度的关系例题多元复合函数......
  • 每日编程一小时(第五天)
    一.问题描述输入一个整数,输出每个数字对应的拼音。当整数为负数时,先输出fu字二.设计思路1.创建一个字符数组2.输入一个字符串(数)3.利用switch来输出每一个字符所对应的读音三.流程图  四.代码实现#include<iostream>usingnamespacestd;intmain(){chara[10......
  • Nvidia Tensor Core-MMA PTX编程入门
    1PTX(ParallelThreadExecution)PTX是什么,Nvidia官方描述为alow-levelparallelthreadexecutionvirtualmachineandinstructionsetarchitecture(ISA),直面意思是低级并行线程执行虚拟机和指令集架构。怎么理解其直面意思,有两个方法。一个方法是借鉴LLVM,熟悉LLV......
  • JS函数:递归函数与迭代函数
    1.递归函数:程序中调用自己的函数程序调用自身的编程技巧称为递归(recursion)。递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归......
  • TypeScript:函数兼容性问题
    函数兼容性大家在JS中经常可以看到这样的代码:lisr.forEach((item)=>{});lisr.forEach((item,index)=>{});就是()的参数有时是可以省略的,而这个正式函数的兼容性性质。说白了就是:多的函数=少的函数;参数少的函数可以赋值给参数多的举个下面例子:typeFun1=(num1......
  • oracle的decode函数实现行转列
    目录oracle的decode函数实现行转列1、decode函数语法2、示例2.1、数据库数据2.2、需求oracle的decode函数实现行转列1、decode函数语法decode(字段,所匹配的值,列所显示的值)2、示例2.1、数据库数据有一个学生表:sys_stu,字段有学生id:stu_id,学生名称:stu_name,学生科目:stu_......
  • dbg 寻找main函数
    方法一,3个push和堆栈平衡设置函数入口设断点,打开程序到达断点后,一直按F8运行到类似下面的地方,F7步进查看是否是main函数。pushedipushesipushdwordptrds:[eax]callproject1.D31040addesp,C如上,因为main函数的参数是3个,所以,在调用main函数之前一定会有3个参数入栈。调用m......
  • python 正则处理字符串,使用函数
    """在正则截取的字符子串基础上,处理字符串Python的re模块提供了re.sub用于替换字符串中的匹配项。语法:re.sub(pattern,repl,string,count=0,flags=0)参数:pattern:正则中的模式字符串。repl:替换的字符串,也可为一个函数。string:要被查找替换的原始字符串。cou......