C++ 学习系列 -- tuple 原理

news2024/9/22 1:06:20

一  可变参数模板 variadic template

        前面的章节 C++ 学习系列 -- 模板 template-CSDN博客 我们介绍了 c++ 中的模板概念,本章则在其基础上介绍了新的概念  可变参数模板 variadic template ,顾名思义,可变参数模板意思为模板参数的类型与数量是变化的,比如:

template<typename ...Args>
void print(Args... args);

template<typename ...Args>
class my_class;

1.1  可变参数模板函数

#include<iostream>

// 递归终止调用的 Print 模板参数个数为 0
void Print()
{
    std::cout << "  " << std::endl;
}

template<typename T, typename ... Args>
void Print(T t, Args...args)
{
    std::cout << t << " "; // 打印出参数  t 的值
    Print(args...); // 递归调用 Print,将除了 t 剩下的一包参数丢入 Print
}

int main()
{
    Print("abc", 'a', 1.0, 66);

    return 0;
}

输出:

1.2 可变参数模板递归继承类

// my_class.h
#include<iostream>

template<typename ...Args>
class MyClass;

// 递归类终止时,template 的参数包为空
template<>
class MyClass<>
{
public:
    MyClass()
    {
        std::cout << "MyClass constructor. " << std::endl;
    }
};

// 递归继承 MyClass 类,基类比派生类的模板参数包少一个 参数 T
template<typename T, typename ...Args>
class MyClass<T, Args...> : private MyClass<Args...>
{
public:
    MyClass(T t, Args... args):data(t),MyClass<Args...>(args...)
    {
         std::cout << "MyClass constructor sizeof(Args) " << sizeof... (Args) << ", data: " << data << std::endl;
    }

private:
    T data;
};

// main.cpp
#include"my_class.h"

int main()
{
   MyClass<double, float, int, long, std::string> my_class(1.22, 1.6, 66, 88, "abcd");

   return 0;
}

输出:

template<>
class MyClass<>;

由输出可以看出,递归继承可变参数的类的模板参数包大小,从 4 -> 3 -> 2 -> 1 -> 0

,最后递归终止时调用的是一个空模板参数包的类 

二  tuple 

1.1  tuple 简介

       在 tuple出现之前,c++ 中的容器,序列容器:vector、deque、list ,关联容器:set、map 等,所存储的元素类型都是单一的(map 的 所有的 key 类型是相同的, 所有的 value 类型是相同的 )。

       如果我们有这样一个需求,需要一个容器或者说用着类似于容器的一个东西,可以存储不同类型的元素,上面的容器是无法满足我们的需求的。考虑到如此,c++ 为我们提供了 pair 类,但是美中不足的是,pair 只能存储两个元素,若是存储两个以上的元素呢? pair 就帮不了我们了。

       在 c++11 之前,前面的需求是无法满足的,c++11则提供了 元组 std::tuple  这个概念(元组在其他类型的语言中也有过,比如 python 中就有元组的概念)  帮助我们实现了这个需求,std::tuple 可以存放任意类型任意个数的元素。

1.2  tuple 原理

    如果让我们来设计元组 std::tuple 的话,底层究竟用什么结构呢?我们可以先复习一下其他一些容器的底层结构:

容器底层结构
vector数组
list链表
map红黑树
unorder_maphash 表

     上述表格中的底层结构都用不了,因为这些底层结构的所有元素类型都是需要一致的。

那让我们抛开底层结构的固化思维,来考虑一下用本章节介绍的 可变模板参数类来实现吧。

    定义变模板参数类,将模板参数包拆分为 第一个模板参数与剩下的模板参数包,在当前类中定义 第一个模板参数的类成员,并在该类构造函数中给该类成员赋值。改了继承一个基类,基类中使用剩下的模板参数包,其余同派生类是相同的。最后,定义一个递归终止的类,递归终止类中无任何模板参数,里面的实现也是空的。

1.3  tuple 源码

    template<std::size_t _Idx, typename _Head>
    struct _Head_base<_Idx, _Head, false>
    {
      constexpr _Head_base()
      : _M_head_impl() { }

      constexpr _Head_base(const _Head& __h)
      : _M_head_impl(__h) { }

      constexpr _Head_base(const _Head_base&) = default;
      constexpr _Head_base(_Head_base&&) = default;

      static constexpr _Head&
      _M_head(_Head_base& __b) noexcept { return __b._M_head_impl; }

      static constexpr const _Head&
      _M_head(const _Head_base& __b) noexcept { return __b._M_head_impl; }
       
      ...
      ...
      ...

      _Head _M_head_impl;
    };
  
// Basis case of inheritance recursion.
  template<std::size_t _Idx, typename _Head>
    struct _Tuple_impl<_Idx, _Head>
    : private _Head_base<_Idx, _Head>
    {
      template<std::size_t, typename...> friend class _Tuple_impl;

      typedef _Head_base<_Idx, _Head> _Base;

      static constexpr _Head&
      _M_head(_Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }

      static constexpr const _Head&
      _M_head(const _Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }

      constexpr _Tuple_impl()
      : _Base() { }

      explicit
      constexpr _Tuple_impl(const _Head& __head)
      : _Base(__head) { }

      ...
      ...
      ...
};

  /**
   * Contains the actual implementation of the @c tuple template, stored
   * as a recursive inheritance hierarchy from the first element (most
   * derived class) to the last (least derived class). The @c Idx
   * parameter gives the 0-based index of the element stored at this
   * point in the hierarchy; we use it to implement a constant-time
   * get() operation.
   */
  template<std::size_t _Idx, typename... _Elements>
    struct _Tuple_impl;

   /**
   * Recursive tuple implementation. Here we store the @c Head element
   * and derive from a @c Tuple_impl containing the remaining elements
   * (which contains the @c Tail).
   */
  template<std::size_t _Idx, typename _Head, typename... _Tail>
    struct _Tuple_impl<_Idx, _Head, _Tail...>
    : public _Tuple_impl<_Idx + 1, _Tail...>,
      private _Head_base<_Idx, _Head>
 {   
      template<std::size_t, typename...> friend class _Tuple_impl;

      typedef _Tuple_impl<_Idx + 1, _Tail...> _Inherited;
      typedef _Head_base<_Idx, _Head> _Base;

      static constexpr _Head&
      _M_head(_Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }

      static constexpr const _Head&
      _M_head(const _Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }

      static constexpr _Inherited&
      _M_tail(_Tuple_impl& __t) noexcept { return __t; }

      static constexpr const _Inherited&
      _M_tail(const _Tuple_impl& __t) noexcept { return __t; }

      constexpr _Tuple_impl()
      : _Inherited(), _Base() { }

      explicit
      constexpr _Tuple_impl(const _Head& __head, const _Tail&... __tail)
      : _Inherited(__tail...), _Base(__head) { }

      ...
      ...
      ...
};
 
 /// Primary class template, tuple
template<typename... _Elements>
class tuple : public _Tuple_impl<0, _Elements...>
{
   typedef _Tuple_impl<0, _Elements...> _Inherited;

   constexpr tuple()
      : _Inherited() { }
   
   ...
   ...
   ...

   constexpr tuple(const _Elements&... __elements)
      : _Inherited(__elements...) { }
   
   ...
   ...
   ...

};

 源码解析:

  •  struct  tuple 是 c++ 中对外提供的元组类,通过源码可以看出,class tuple 是一个可变模板参数类,其模板参数可以是多个类型不同的参数,该类继承自 class _Tuple_impl
template<typename... _Elements>
class tuple : public _Tuple_impl<0, _Elements...>;
  • struct  _Tuple_impl 有三个模板参数,_Idx 表示元组中当前元素的下标,从 0 开始,_Head 表示当前存储的元素类型, Tail 表示剩下的模板参数包。该类继承了两个基类,分别是 class _Tuple_impl(就算该类本身,但是其模板参数是不同的),该类不存储元素, _Head_base 是用来存储元素的。 
template<std::size_t _Idx, typename _Head, typename... _Tail>
    struct _Tuple_impl<_Idx, _Head, _Tail...>
    : public _Tuple_impl<_Idx + 1, _Tail...>,
      private _Head_base<_Idx, _Head>;
  • 与递归函数需要终止条件一样,递归类的继承也需要终止类,下面就算递归终止类,在该类中,模板参数有两个 _Idx 表示当前元素的下标,_Head 表示元组中最后一个元素的类型,该类不存储元素,是通过继承的 _Head_base 来实现元素的存储的
template<std::size_t _Idx, typename _Head>
    struct _Tuple_impl<_Idx, _Head>
    : private _Head_base<_Idx, _Head>;
  •  struct _Head_base 是 struct _Tuple_impl 的基类,该类又两个模板参数:_Idx 表示元素的下标,_Head 表示当前存储元素的类型,该类的主要作用是在内存中开辟一块内存空间,用来存储成员变量 _Head  _M_head_impl;
    template<std::size_t _Idx, typename _Head>
    struct _Head_base<_Idx, _Head, false>;

  make_tuple 函数 可以帮用户在使用中只传入参数,来构造一个 tuple, 其源码如下:  

 template<typename... _Elements>
    constexpr tuple<typename __decay_and_strip<_Elements>::__type...>
    make_tuple(_Elements&&... __args)
    {
      typedef tuple<typename __decay_and_strip<_Elements>::__type...>
	__result_type;
      return __result_type(std::forward<_Elements>(__args)...);
    }

  通过  __decay_and_strip 将模板参数类型获取出来,定义返回类型 __result_type  ,实际就是 tuple 类型,再利用万能转发 std::forward 将参数转发给返回 类型 __result_type ,构造一个临时对象 return 。函数的返回类型用 constexpr 修饰,表示编译器就将该函数执行创造出 tuple 对象。

上面大多数代码都能看懂,__decay_and_strip 有点奇怪,源码如下:

template<typename _Tp>
    struct __strip_reference_wrapper<reference_wrapper<_Tp> >
    {
      typedef _Tp& __type;
    };

  template<typename _Tp>
    struct __decay_and_strip
    {
      typedef typename __strip_reference_wrapper<
	typename decay<_Tp>::type>::__type __type;
    };

/// decay
  template<typename _Tp>
    class decay
    {
      typedef typename remove_reference<_Tp>::type __remove_type;

    public:
      typedef typename __decay_selector<__remove_type>::__type type;
    };

  通过源码可以看出,__decay_and_strip 就是将模板参数的类型萃取出来,如果模板参数是引用类型,该函数也会将 引用类型去除掉。

1.4  tuple 使用

#include<iostream>
#include<tuple>

int main()
{
     // 1. 直接构造 tuple 对象
    std::tuple<float, int, double, long, char, std::string>  tup(1.2, 33, 2.3, 66, 'a', "abcde");
    std::cout << std::get<0>(tup) << " ";
    std::cout << std::get<1>(tup) << " ";
    std::cout << std::get<2>(tup) << " ";
    std::cout << std::get<3>(tup) << " ";
    std::cout << std::get<4>(tup) << " ";
    std::cout << std::get<5>(tup) << std::endl;


    // 2. 利用 make_tuple 构造 tuple 对象

    std::tuple<float, int, double, long, char, std::string> tp = std::make_tuple(1.2, 33, 2.3, 66, 'a', "abcde");
    std::cout << std::get<0>(tp) << " ";
    std::cout << std::get<1>(tp) << " ";
    std::cout << std::get<2>(tp) << " ";
    std::cout << std::get<3>(tp) << " ";
    std::cout << std::get<4>(tp) << " ";
    std::cout << std::get<5>(tp) << std::endl;

    return 0;
}

输出:

三  实现简单的 tuple

// my_tuple.h
template<size_t _Idx, typename ...Args>
struct my_tuple_impl;

template<size_t _Idx,typename _Head, typename ...Args>
struct my_tuple_impl<_Idx, _Head, Args...> : private my_tuple_impl<_Idx+1, Args...>
{
    typedef my_tuple_impl<_Idx+1,Args...> _inheritd;
public:
    my_tuple_impl(const _Head& head, const Args&... args):m_head(head),_inheritd(args...)
    {

    }

    _Head&  head()
    {
        return m_head;
    }

    _inheritd& tail()
    {
        return *this;
    }

private:
    _Head m_head;
};


template<size_t _Idx,typename _Head>
struct my_tuple_impl< _Idx,_Head>
{
public:
    my_tuple_impl(const _Head& head):m_head(head)
    {

    }

    const _Head&  head() const
    {
        return m_head;
    }

private:
    _Head m_head;
};

template< typename ... Args>
struct my_tuple : public my_tuple_impl<0, Args...>{
    typedef  my_tuple_impl<0, Args...>  impl;
public:
    my_tuple(const Args&... args):impl(args...)
    {

    }

};

// main.cpp
#include<iostream>
#include"my_tuple.h"

int main()
{
    my_tuple<float, int, double, long, char, std::string> tuple(1.2, 33, 2.3, 66, 'a', "abcde");

    std::cout << tuple.head() << " ";
    std::cout << tuple.tail().head() << " ";
    std::cout << tuple.tail().tail().head() << " ";
    std::cout << tuple.tail().tail().tail().head() << " ";
    std::cout << tuple.tail().tail().tail().tail().head() << " ";
    std::cout << tuple.tail().tail().tail().tail().tail().head() << " ";

    return 0;
}

输出:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1365786.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅谈智能照明系统调试阶段节能方案的探究与产品选型

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 【摘要】针对当今智能照明系统调试完成前能源浪费的问题&#xff0c;本文结合工程案例&#xff0c;分析研究了智能照明系统调试阶段的节能方法&#xff0c;提出了采用时间控制器来解决能源及人工浪费等问题的方式。实践证明&a…

微众区块链观察节点的架构和原理 | 科普时间

践行区块链公共精神&#xff0c;实现更好的公众开放与监督&#xff01;2023年12月&#xff0c;微众区块链观察节点正式面向公众开放接入功能。从开放日起&#xff0c;陆续有多个观察节点在各地运行&#xff0c;同步区块链数据&#xff0c;运行区块链浏览器观察检视数据&#xf…

Kafka(六)消费者

目录 Kafka消费者1 配置消费者bootstrap.serversgroup.idkey.deserializervalue.deserializergroup.instance.idfetch.min.bytes1fetch.max.wait.msfetch.max.bytes57671680 (55 mebibytes)max.poll.record500max.partition.fetch.bytessession.timeout.ms45000 (45 seconds)he…

【STM32】STM32学习笔记-DMA数据转运+AD多通道(24)

00. 目录 文章目录 00. 目录01. DMA简介02. DMA相关API2.1 DMA_Init2.2 DMA_InitTypeDef2.3 DMA_Cmd2.4 DMA_SetCurrDataCounter2.5 DMA_GetFlagStatus2.6 DMA_ClearFlag 03. DMA数据单通道接线图04. DMA数据单通道示例05. DMA数据多通道接线图06. DMA数据多通道示例一07. DMA数…

STM32(HAL库) CubeMX+Keil5 建立工程

STM32&#xff08;HAL库&#xff09; CubeMXKeil5 建立工程 目标选择 菜单栏 File 新建工程打开工程退出软件 Window 输出窗口的开启软件字体设置 Help 软件帮助文档检查软件更新管理MCU 已存在工程&#xff08;Existing Projects&#xff09; 最近打开过的工程(Recent Open…

如何科学评价视频生成模型?AIGCBench:全面可扩展的视频生成任务基准来了!

AIGC领域正迅速发展&#xff0c;特别是在视频生成方面取得了显著进展。本文介绍了AIGCBench&#xff0c;这是一个首创的全面而可扩展的基准&#xff0c;旨在评估各种视频生成任务&#xff0c;主要关注图像到视频&#xff08;I2V&#xff09;生成。AIGCBench解决了现有基准的局限…

苹果显示连接iTunes是什么意思?你知道吗?答案来了!

相信使用苹果手机的小伙伴都听说过iTunes软件&#xff0c;但是可能还有小部分人不知道iTunes是什么&#xff0c;以及苹果设备上显示连接itunes是什么意思。对于使用iTunes进行数据备份、恢复等操作的用户来说&#xff0c;出现这个提示意味着您的苹果设备已经与电脑成功连接&…

vue-springboot基于java的实验室安全考试系统

本系统为用户而设计制作实验室安全考试系统&#xff0c;旨在实现实验室安全考试智能化、现代化管理。本实验室安全考试管理自动化系统的开发和研制的最终目的是将实验室安全考试的运作模式从手工记录数据转变为网络信息查询管理&#xff0c;从而为现代管理人员的使用提供更多的…

【Docker基础一】Docker安装Elasticsearch,Kibana,IK分词器

安装elasticsearch 下载镜像 查看版本&#xff1a;Elasticsearch Guide [8.11] | Elastic # 下载镜像 docker pull elasticsearch:7.17.16 # 查看镜像是否下载成功 docker images创建网络 因为需要部署kibana容器&#xff0c;要让es和kibana容器互联 # 创建一个网络&…

并发(10)

目录 61.ReentrantReadWriteLock底层读写状态如何设计的&#xff1f; 62.读锁和写锁的最大数量是多少&#xff1f; 63.本地线程计数器ThreadLocalHoldCounter是用来做什么的&#xff1f; 64.写锁的获取与释放是怎么实现的&#xff1f; 65.读锁的获取与释放是怎么实现的&…

【算法】递归算法理解(持续更新)

这里写目录标题 一、递归算法1、什么情况下可以使用递归&#xff1f;2、递归算法组成部分3、案例&#xff1a;求n的阶乘4、编写一个递归函数来计算列表包含的元素数。5、通过递归找到列表中最大的数字。6、通过递归的方式实现二分查找算法。 一、递归算法 递归&#xff08;Rec…

浅谈LCD屏幕引脚定义识别

学习单片机&#xff0c;总要驱动LCD屏幕&#xff0c;但是对于没有引脚定义的LCD屏幕该如何应对&#xff1f; 本人研究不深&#xff0c;只谈体会。 比如下面这款屏幕 一、第一种方法 百度大法查引脚定义。查询条件可以是FPC上的丝印&#xff0c;或者是屏幕的尺寸&#xff0c;引脚…

sublime如何取消运行代码状态

sublime如何取消运行代码状态 解决方案待续、更新中 解决方案 1 顶部取消: 工具-----取消编译 这个看自己编译器sublime取消编译是否可用,可用则用 ,否则使用下面方法 2 底部栏取消–如图所示: 取消成功: 待续、更新中 ————————————————————— 以上就…

2024前端炫酷源码分享(附效果图及在线演示)

分享10款非常有趣的前端特效源码 其中包含css动画特效、js原生特效、svg特效以及小游戏等 下面我会给出特效样式图或演示效果图 但你也可以点击在线预览查看源码的最终展示效果及下载源码资源 GSAP-火箭动画特效 GSAP 火箭动画 当氮气充足的情况下 火箭会冲出 并继续飞行 图片…

【Java】设计模式之顺序控制

实际开发中&#xff0c;有时候一些场景需求让多个线程按照固定的顺序依次执行。这个时候就会使用这种模式。 这种模式说白了&#xff0c;就是给线程设定不同的条件&#xff0c;不符合条件的话&#xff0c;就算线程拿到锁也会释放锁进入等待&#xff1b;符合条件才让线程拿到锁…

【解决方案】电能质量在线监测装置和防孤岛保护装置在特斯拉工厂分布式光伏项目的应用

摘要&#xff1a; 随着全球对可再生能源的关注度不断提高&#xff0c;分布式光伏发电系统在近年来得到了广泛应用。分布式光伏发电系统具有环保、灵活等优势&#xff0c;能够有效地缓解能源短缺和环境污染问题。同时&#xff0c;电能质量在线监测装置和防孤岛保护装置在分布式…

leetcode:412. Fizz Buzz(python3解法)

难度&#xff1a;简单 给你一个整数 n &#xff0c;找出从 1 到 n 各个整数的 Fizz Buzz 表示&#xff0c;并用字符串数组 answer&#xff08;下标从 1 开始&#xff09;返回结果&#xff0c;其中&#xff1a; answer[i] "FizzBuzz" 如果 i 同时是 3 和 5 的倍数。a…

开心自走棋:使用 Laf 云开发支撑数百万玩家

先介绍一下开心自走棋 开心自走棋是一款剑与魔法的烧脑自走棋游戏。以著名的魔幻世界观为蓝本&#xff0c;采用了轻松可爱的画面风格&#xff0c;精致细腻的动画和特效来还原魔兽之战。 现在市面上自走棋游戏多是 PvP 玩法为主&#xff0c;而开心自走棋是以 PvE 玩法为主的&a…

刷了四百道算法题,我在项目里用过哪几道呢?

大家好&#xff0c;我是老三&#xff0c;今天和大家聊一个话题&#xff1a;项目中用到的力扣算法。 不知道从什么时候起&#xff0c;算法已经成为了互联网面试的标配&#xff0c;在十年前&#xff0c;哪怕如日中天的百度&#xff0c;面试也最多考个冒泡排序。后来&#xff0c;…

VTK将二维图像向三维空间中无参数化的曲面表面进行纹理映射(含代码)

实现纹理映射主要是建立纹理空间与模型空间、模型空间与屏幕空间之间的映射关系(见图 6-28)&#xff1a; 其中纹理空间可以定义为u-v 空间&#xff0c;每个轴标范围为 (0.1)。其中对于一个纹理图像&#xff0c;其左下角 v 标为 0.0)&#xff0c;右上角标为 1.1)。而对于简单的参…