[C++]高效使用c++11--理解auto类型推导

2023-10-31

推导类型

1. 理解类型推导

auto的推导方式和template是一样的,所以我们首先来介绍template是如何推导类型的。

template <typename T>
void f(const T& orig) {
    cout << __PRETTY_FUNCTION__ << endl;
    cout << typeid (orig).name() << endl;
    cout << typeid (T).name() << endl;
}
    int x = 10;
    f(x);
/*
void f(const T &) [T = int]
i
i
*/

T和orig的类型一样的,这很奇怪吧。实际上,template类型推导有三个情况:

    1. orig是一个指针或者引用类型,但不是全局引用(universal reference)
    1. orig是一个全局引用。
    1. orig即使不是指针也不是引用。
template <typename T>
void f(ParamType param);
f(expr)

情况1 :ParamType是一个指针或者引用类型,但不是全局引用(universal reference)

在这种情况下,

    1. 如果expr的类型是一个引用,忽略引用的部分。
    1. 把expr的类型与ParamType的类型比较,用来判断T的类型。

例如:

template <typename T>
void f(T& param) {
    cout << __PRETTY_FUNCTION__ << endl;
}
int main(int argc, char *argv[]) {
    int y = 10;
    f(y);
    const int x = y;
    f(x);
    const int& z = y;
    f(z); // ignore the reference.
    return 0;
}
void f(T &) [T = int]
void f(T &) [T = const int]
void f(T &) [T = const int]

这就是为什么一个const对象传给模板后是安全的,因为const性质会成为模板推导的一部分。

我们注意到第三个例子中,T被推导为const int,是因为忽略了&, 如果不这样,ParamType 会被推导为const int&&,这是不被允许的。

我们这里提及的都是左值引用,实际上右值引用也是一样的,但是右值引用只能传递给右值引用,虽然这个类型推导关系不大。

我们来做一个小小的修改。

template <typename T>
void f(const T& param) {
    cout << __PRETTY_FUNCTION__ << endl;
}
int main(int argc, char *argv[]) {
    int y = 10;
    f(y);
    const int x = y;
    f(x);
    const int& z = y;
    f(z); // ignore the reference.
    return 0;
}
void f(const T &) [T = int]
void f(const T &) [T = int]
void f(const T &) [T = int]

同样的,T的引用被忽略了,const属性也被忽略了。因为对于param而言总是const。

对于指针:

template <typename T>
void f(T* param) {
    cout << __PRETTY_FUNCTION__ << endl;
}
int main(int argc, char *argv[]) {
    int y = 10;
    f(&y);
    const int x = y;
    f(&x);
    const int& z = y;
    f(&z); // ignore the reference.
    const int* p = &z;
    f(p);
    return 0;
}
void f(T *) [T = int]
void f(T *) [T = const int]
void f(T *) [T = const int]
void f(T *) [T = const int]

分析也一样。

情况2:ParamType是一个全局引用。

全局引用是T&&类型,也就是右值引用。这种情况稍微有一些不同。

    1. 如果expr是一个左值引用,那么T和ParamType都会被推导为左值引用。
    1. 如果expr是一个右值,那么就使用通常的推导方法。

例如:

template <typename T>
void f(T&& param) {
    cout << __PRETTY_FUNCTION__ << endl;
}
int main(int argc, char *argv[]) {
    int y = 10;
    f(y);
    const int x = y;
    f(x);
    const int& z = y;
    f(z);
    f(10);
    return 0;
}
void f(T &&) [T = int &] //  param为int &
void f(T &&) [T = const int &]
void f(T &&) [T = const int &]
void f(T &&) [T = int] //  param 为int&&

关键是,当使用全局引用时,类型推导会区别于右值引用和左值引用。

情况3: ParamType既不是指针也不是引用时

    1. 如果expr是一个引用,忽略引用。
    1. 如果expr是一个const, 忽略const。如果expr是一个volatile,忽略它。

例如:

template <typename T>
void f(T param) {
    cout << __PRETTY_FUNCTION__ << endl;
}
int main(int argc, char *argv[]) {
    int y = 10;
    f(y);
    const int x = y;
    f(x);
    const int& z = y;
    f(z);
    return 0;
}
void f(T) [T = int]
void f(T) [T = int]
void f(T) [T = int]

因为这里是按值传递,所以本身对象的性质并不会传递给他的拷贝对象。

前面提到在类型推导时,const引用或者constpointer的const属性会被保留,但如果expr是一个const指针指向const对象,而expr被传递给按值传递的函数,那么情况会怎么样?

template <typename T>
void f(T param) {
    cout << __PRETTY_FUNCTION__ << endl;
}
int main(int argc, char *argv[]) {
    const char* const ptr = "yanzexin";
    f(ptr);
    return 0;
}
void f(T) [T = const char *]
    const char* const ptr = "yanzexin";

第一个const,指不可以修改这个指针指向的对象。
第二个const,指不可以修改指针指向的对象的值。

在传递过程中,指针的const属性被保留,但指针指向对象的const属性被忽略了。

对于数组

数组通常情况下都会被理解为指向数组第一个元素的指针。

template <typename T>
void f(T param) {
    cout << __PRETTY_FUNCTION__ << endl;
}
int main(int argc, char *argv[]) {
    const char name[] = "stary";
    f(name);
    const int phone[] = {1, 2};
    f(phone);
    return 0;
}
void f(T) [T = const char *]
void f(T) [T = const int *]

但如果我们真的希望传递的是一个数组,我们可以使用引用。

template <typename T>
void f(T& param) {
    cout << __PRETTY_FUNCTION__ << endl;
}
int main(int argc, char *argv[]) {
    const char name[] = "stary";
    f(name);
    const int phone[] = {1, 2};
    f(phone);
    return 0;
}
void f(T &) [T = char const[6]]
void f(T &) [T = int const[2]]

T会真正的被推导为数组。由此param的类型实际上就是const char&[6]。 所以我们甚至可以直接推导出数组的大小。

template <typename T, size_t N>
void f(T(&) [N]) {
    cout << __PRETTY_FUNCTION__ << endl;
}
int main(int argc, char *argv[]) {
    const char name[] = "stary";
    f(name);
    const int phone[] = {1, 2};
    f(phone);
    return 0;
}
void f(T (&)[N]) [T = const char, N = 6]
void f(T (&)[N]) [T = const int, N = 2]

对于函数

函数实际上也是和数组一样,会被自动推导到指针。如果希望推导成引用,方法是一样的。

template <typename T>
void f(T param) {
    cout << __PRETTY_FUNCTION__ << endl;
}
void func(double, int) {
}
int main(int argc, char *argv[]) {
    f(func);
    return 0;
}
void f(T) [T = void (*)(double, int)]
template <typename T>
void f(T& param) {
    cout << __PRETTY_FUNCTION__ << endl;
}
void func(double, int) {
}
int main(int argc, char *argv[]) {
    f(func);
    return 0;
}
void f(T &) [T = void (double, int)]

关键点:

    1. 当推导类型为指针或非全局引用,引用性会被忽略。
    1. 当推导类型为全局引用时,左值引用被推导为左值引用,右值引用被推导为右值引用。
    1. 当为按值传递时,推导的引用和const属性被忽略。
    1. 数组和函数会被推导为指针。

2. 理解auto的类型推导

auto就是使用template的推导方式,实际上存在一种直接的映射,把template的推导和auto的类型推导联系起来。

int main(int argc, char *argv[]) {
  auto x = 17; // x = int
  auto& y = x; // y = int&
  const auto& i = x; // i = const int&
  auto z = y; // z = int
  const auto f = y; // f = const int
  auto& m = i; // m = const int&
  auto&& t = i; // t = int&
  auto&& t1 = 10; // t1 = int&& 
  return 0;
}
int main(int argc, char *argv[]) {
  int A[3] = {1, 2, 3};
  auto a = A; // a = int *
  auto& a1 = A; // a1 = int (&)[3]
  return 0;
}

可以发现和auto确实没什么区别。

实际上,auto和template只有一个很大的区别。

在C++11中给出了一个全新的初始化方法,叫参数列表。

int main(int argc, char *argv[]) {
  int a0 = (1); // a0 = int
  auto a1 = (1); // a1 = int
  int a2 = {1}; // a0 = int
  auto a3 = {1}; // a3 = std::initializer_list<int>
  return 0;
}

但需要注意的是,参数列表在auto中会被推导为 std::initializer_list。

auto中可以把带{}的推导出来,但template是推导不出来的,编译无法通过。

template <typename T>
void test(T orig) {
  cout << __PRETTY_FUNCTION__ << endl;
}

int main(int argc, char *argv[]) {
  test((1));
  test({1}); // error!
  return 0;
}

在C++11中,这就已经没什么问题了。但C++14中还有一小部分的问题需要讨论。

C++14允许auto去指示函数的返回类型,并且允许在lambda表达式中使用auto参数(C++11中不允许),但这个auto的推导是使用template推导方式的,不是使用auto本身的推导方式。这也就是说,返回一个{},是不能通过编译的。

int main(int argc, char *argv[]) {
  list<int> ls;
  auto l = [&ls](auto&& list) {
    ls.insert(ls.end(), list);
  };
  l(1);
  return 0;
}
int main(int argc, char *argv[]) {
  list<int> ls;
  auto l = [&ls](auto&& list) {
    ls = list;
  };
  //  l({1, 2, 3, 4}); error!
  l(list<int> {1, 2, 3, 4});
  return 0;
}

关键点:

auto推导通常情况下和template的推导是一样的,除非一个变量被声明并且是使用的初始化列表。

3. 理解decltype

template <typename container, typename index>
auto re(container& con, index i) {
  return con[i];
}

int main(int argc, char *argv[]) {
  vector<int> a {1, 2, 3, 4, 5, 6};
  re(a, 3) = 10; // 无法通过编译
  cout << a[3] << endl;
  return 0;
}

返回类型是auto,就有前面提到的一样vector []operator返回的是引用类型,但auto会自动把引用类型忽略,从而无法进行修改。但如果我们希望这个函数返回的是真正的引用类型,该怎么做呢?

使用decltype,显式表明返回类型。以下这个实现方法能实现但不够好。原因我们暂时不去解释。

template <typename container, typename index>
auto re(container& con, index i) ->decltype(con[i]) {
  return con[i];
}

int main(int argc, char *argv[]) {
  vector<int> a {1, 2, 3, 4, 5, 6};
  re(a, 3) = 10;
  cout << a[3] << endl;
  return 0;
}

一种更简单的做法是:

template <typename container, typename index>
decltype(auto) re(container&& con, index i) {
  return con[i];
}
int main(int argc, char *argv[]) {
  vector<int> a{1, 2, 3, 4, 5};
  re(a, 3) = 10;
  cout << re(a, 3) << endl;
  return 0;
}

auto表明这个类型需要推导,decltype表示推导使用decltype的方法,也就是根据他实际的类型来返回。这种方法更加好。但需要c++14。

decltype(auto) 不仅可以用作函数的返回类型,也可以用作变量的声明。

int main(int argc, char *argv[]) {
  const int a = 10;
  decltype(auto) b = a;
  return 0;
}

b也是const int!

template <typename container, typename index>
decltype(auto) re(container& con, index i) {
  return con[i];
}

这个容器只能传递非const左值引用。右值引用不能捆绑左值引用。(除非是const的左值引用)

我们之前提到的

template <typename container, typename index>
decltype(auto) re(container&& con, index i) {
  return con[i];
}

做法不是太好。

具体的做法应该是

template <typename container, typename index>
decltype(auto) re(container& con, index i) {
  return forward<container>(con)[i];
}

实际上是这样的C++给出标准,有对于右值引用既可以是左值也可以是右值,有名字的就是左值,没名字的就是右值。
forward只能在模板函数中私用, 它原本是什么类型就返回什么类型。于是我们可以做出这样的实例,来解释。

#include <iostream>
#include <vector>
using namespace std;
class test {
public:
  test() {
    cout << "construct" << endl;
  }
  test(test&& orig) {
    cout << "move" << endl;
  }
  test(test& orig) {
    cout << "copy" << endl;
  }
};
template <typename T>
decltype(auto) f(T&& orig) {
  return forward<T>(orig);
}
int main(int argc, char *argv[]) {
  test b;
  test c = f(b);
  cout << endl;
  test m = f(test());
  return 0;
}
construct
copy

construct
move
Program ended with exit code: 0

结果就非常明显了。
具体关于move和forward的使用细节见:
c++11 中的 move 与 forward

最后一个问题是,decltype((x))为被推导为int&!这里需要注意的是,不要返回临时对象的引用。否则可能会出问题。

decltype(auto) f() {
  int x = 0;
  return (x);
}

这会导致不确定性行为。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[C++]高效使用c++11--理解auto类型推导 的相关文章

  • C#网络编程,多个客户端连接服务器端并发送消息

    最近学习进度到了C 网络编程 在学习这一章节的知识点 写了一些小demo 此次发表的为服务器监听端口 和多个客户端连接 获取多个客户端发来的消息 服务器端代码 using System Net using System Net Socket
  • gcc -l参数和-L参数

    l参数和 L参数 l参数就是用来指定程序要链接的库 l参数紧接着就是库名 那么库名跟真正的库文件名有什么关系呢 就拿数学库来说 他的库名是m 他的库文件名是libm so 很容易看出 把库文件名的头lib和尾 so去掉就是库名了 好了现在我
  • 【C++拾遗之八】#pragmaonce与#ifndef的用法总结

    宏定义 一 两种宏定义的功能 二 两种宏定义的用法 三 两种宏定义的区别 一 两种宏定义的功能 ifndef 和 pragma once都是C C 中的两种宏定义 它们的作用是为了避免同一个头文件被多次包含 include note 只能保
  • linux通信机制signal()函数详解

    我们来谈一谈signal 函数的作用 linux系统提供了signal 和sigaction 两个函数来改变对于信号的处理方法 其中signal 是一个基于sigaction 系统调用的glibc库函数 其行为在不同的linux操作系统存在
  • Qt Creator下载和安装(详细教程)

    简介 Qt是跨平台的图形开发库 目前由Digia全资子公司 Qt Company 独立运营 官方网址 http www qt io 也可以访问Qt项目域名 http qt project org Qt本身支持众多操作系统 从通用操作系统Li
  • LFSR(线性反馈移位寄存器)的C++实现

    n级线性反馈移位寄存器 这里以n 7为例 假设初始状态为1000000 使用C 标准库中的bitset类来实现 bitset类的用法详解 点击打开链接 include
  • STL的一些基本背景了解。

    STL源代码的头文件一般都是内联模式的 现在简单的把stl的集中类型进行分类说明一下 1 容器类 一般分为关联式容器和顺序式容器 典型的例子的就是vector为典型的顺序式容器 对于stl来说主要采用向量 链表 二叉树以及他们的组合为底层存
  • [C++]高效使用c++11--理解auto类型推导

    推导类型 1 理解类型推导 auto的推导方式和template是一样的 所以我们首先来介绍template是如何推导类型的 template
  • gcc/g++交叉编译*.c/*.cpp程序时的配置

    CFLAGS I PWD src CFLAGS I third party hisi include CFLAGS L third party hisi lib CXXFLAGS I PWD src CXXFLAGS I third par
  • 位运算符(一):C/C++位运算符

    位运算是指按二进制进行的运算 在程序中 常常需要处理二进制位的问题 C C 语言提供了6个位操作运算符 这些运算符只能用于整型操作数 即只能用于带符号或无符号的char short int与long类型 在实际应用中 建议用unsigned
  • C#中的BeforeFieldInit

    今天学习设计模式中的单例模式 无意间发现了这个标志BeforeFieldInit 于是简单地搜索了一下 总结出如下内容 The C specification states The static constructor for a clas
  • 【FFmpeg学习笔记一】FFmpeg简介

    Fmpeg简介 一 FFmpeg简介 二 FFmpeg函数库 三 FFmpeg用例 四 相关链接 一 FFmpeg简介 FFmpeg是一款自由软件 核心功能就是音视频编解码 其中的 FF 指的是 Fast Forward 快速前进 FFmp
  • KEIL编译出现错误“source file is not valid utf-8”

    KEIL编译出现错误 source file is not valid utf 8 在外面复制了一段代码 c文件一直报错source file is not valid utf 8的错误 经查找原因就是 文件中出现中文符号导致的 特别是中文
  • __builtin_expect, __builtin_unreachable和__builtin_prefetch

    builtin expect 该指令是gcc引入的 就是允许代码编写者把最有可能执行的分支告诉编译器 标准写法是 bultin expect exp n 意思是exp n的概率很大 这样编译器可以对代码进行优化 减少指令跳转带来的性能下降
  • Qt与VS的对比(1)

    对话框篇 QT与VS创建对话框的方式非常相似 都需要创建一个对话框类 在需要显示该对话框的地方包含对应的头文件 显示即可 区别 QT显示模态对话框使用函数QDialog exec VS显示模态对话框使用函数CDialog DoModal Q
  • 记一次线性插值方法(Mathf.Lerp())的使用体会

    对Mathf Lerp 方法使用体会源于一次开发游戏对警报灯闪烁问题进行处理时 public static float Lerp float from float to float t 分析一下对线性插值函数的认识 就是在from与to之间
  • C# 理解Thread.Sleep()方法 (转载学习)

    我们可能经常会用到 Thread Sleep 函数来使线程挂起一段时间 那么你有没有正确的理解这个函数的用法呢 思考下面这两个问题 1 假设现在是 2008 4 7 12 00 00 000 如果我调用一下 Thread Sleep 100
  • c语言之字符串数组

    还是在写图的存储结构的时候 遇到了问题 就是如何在一个数组中存放字符串 我相信这个问题 对于面向对象的编程语言来说 轻而易举 比如对于Java来说 直接像下面就可以了 但是c语言没有String这个类型 能想到存放字符串的数据类型就是cha
  • 利用VTK显示PLY网格模型文件

    define vtkRenderingCore AUTOINIT 2 vtkRenderingOpenGL2 vtkInteractionStyle include
  • QT文件读取路径

    最近在弄中兴的一个程序大赛 用QT读取XML文件的编程 在编程中发现QT文件读取路径与VS有不同之处 我们提供给QFile的文件路径无非就是绝对路径和相对路径 绝对路径是绝对没问题的 不过相对路径就得小心了 谈到相对路径 需要注意区分进程所

随机推荐

  • soso313.cn、dao234.com等劫持浏览器,tlntsvi_1547.exe、ydzyh.exe、scvhost.exe等做怪

    soso313 cn dao234 com等劫持浏览器 tlntsvi 1547 exe ydzyh exe scvhost exe等做怪 一位网友的电脑中了病毒 用超级巡警查杀后 每次开机进入Windows桌面后都会弹出对话框 提示找不到
  • 2021年IDEA通过jdbc连接MySql的方式

    很崩溃的几天 这篇文章解决一个问题 在运行 Class forName com mysql cj jdbc Driver 时报错 java lang ClassNotFoundException com mysql cj jdbc Driv
  • 使用YAML代替Properties

    23 6 使用YAML代替Properties YAML是JSON的一个超集 也是一种方便的定义层次配置数据的格式 无论你何时将SnakeYAML 库放到classpath下 SpringApplication类都会自动支持YAML作为pr
  • websocket心跳的实现(包括全部代码)

    本文主要讲的是如果设计websocket心跳已经需要考虑哪些问题 前言 在使用websocket的过程中 有时候会遇到客户端网络关闭的情况 而这时候在服务端并没有触发onclose事件 这样会 多余的连接 服务端会继续给客户端发数据 这些数
  • LSTM多步时间序列预测+区间预测(附代码实现)

    LSTM单步时间序列预测文章 联系方式在此文章 511条消息 时间序列预测 LSTM模型 附代码实现 lstm预测模型 噜噜啦啦咯的博客 CSDN博客 模型原理 长短时记忆网络 Long short term memory LSTM 是一种
  • 剑指 Offer II 105. 岛屿的最大面积-经典BFS和DFS问题

    https leetcode cn com problems ZL6zAn 解题思路 采用BFS 需要用到额外空间队列或者栈 采用DFS 不需要额外空间 注意 grid的更新 DFS class Solution public int ma
  • antd Form组件initialValues属性在React17中延迟渲染问题

    需求是点击修改框 然后显示一个Modal框 里面有点击部门的相关权限 把点击的数据传进Form组件initialValues 出现bug 数据改变了但是默认值为上一次点击的值 可以看到化工学院的值变成上一次点击的信息学院的值 再次点击恢复正
  • 离线强化学习(Offline RL)系列6: (采样效率) OfflineRL中的样本选择策略(Sample Selection Strategies)

    论文原文 https offline rl neurips github io 2021 pdf 33 pdf 我们知道在强化学习中 不同的样本选择对算法的影响比较大 最典型的莫过于使用优先级经验回放 PER 技术提高算法对采样样本的选择效
  • HDFS文件读写流程

    HDFS读文件流程 1 客户端向NameNode发送读文件请求 NameNode返回文件的数据块信息 对于每一个数据块 元数据节点返回保存数据块的数据节点的地址 2 文件系统返回FSDataInputStream给客户端 用来读取数据 3
  • 渲染出现的 [Object object] 错误

    可能原因是 String Object导致Object是转换为String 默认结果就成了 Object object 可能的解决方案 在自定义对象定义toString 并返回任何你想要在输出中看到的 使用JSON stringify ob
  • 每日一练 C++ 熊孩子拜访

    本题较为简单 因为只有一段子序列被倒序了 故只需要确定子序列的头和尾就完全决定了子序列 而在整段序列中 余子序列满足递增 子序列内部满足递减 尾部与正常序列不满足递减 根据以上规律 先通过递增筛查 首个不满足递增的位置就是子序列的头部 然后
  • seaborn绘制箱线图和折线图

    利用seaborn绘制箱线图和折线图 均值连线 过程出现的问题 1 问题1 参考python seaborn 共享x轴画图 数据可视化对代码进行修改 画图部分代码 fig plt figure figsize 18 6 ax1 fig ad
  • android 隐藏系统键盘

    http blog sina com cn s blog 87479ba60101akfh html 已验证 public static void closeBoard Context mcontext InputMethodManager
  • Caused by: java.io.NotSerializableException:

    详细报错信息如下 This application has no explicit mapping for error so you are seeing this as a fallback Mon Oct 18 10 44 53 CST
  • 隐私计算分类

    在大数据时代中 海量的数据的交叉计算和人工智能的发展为各行各业提供了更好的支持 但这些被使用的数据往往包含用户的隐私数据 或企业 机构的内部数据 这些数据由于数据安全和隐私的考虑 往往是不对外开发 例如政府数据由于政策保密性完全不能对外公布
  • 【H.264/AVC视频编解码技术详解】二十、H.264的去块滤波算法

    H 264 AVC视频编解码技术详解 视频教程已经在 CSDN学院 上线 视频中详述了H 264的背景 标准协议和实现 并通过一个实战工程的形式对H 264的标准进行解析和实现 欢迎观看 纸上得来终觉浅 绝知此事要躬行 只有自己按照标准文档
  • 两种办法解决 make: Warning: File “xxx“ has modification time yyy s in the future 的问题

    一 引言 最近在工作中 在本地将代码文件上传到远端服务器 在远端服务器进行 make 编译的时候 会报这样的错 make Warning File xxx has modification time yyy s in the future
  • 2021年7月19日--7月25日(调试Osgearth33+抄写osg/osgearth源码,共20小时,合计829小时,剩9171小时)

    继续按计划进行 其他随意 完成情况 除抄写osg osgearth源码最后计算外 周一 1 整合gis引擎 1小时 2 网络视频教程1小时 合计2小时 周二 18 40 19 20 osgEarth33调试一节 40分钟 19 36 20
  • 人生,天命,自己

    前提摘要 我现在刚刚毕业 正在找工作 疯狂投简历 目前还没有消息 但是我最近有一个想法 在这个想法之上正在研究一个关于文档关联的新东西 简单来说就是利用相关性算法 然后 使用py代码来实现文档关联 为什么我会公开说出我的研究 或者是我发表关
  • [C++]高效使用c++11--理解auto类型推导

    推导类型 1 理解类型推导 auto的推导方式和template是一样的 所以我们首先来介绍template是如何推导类型的 template