(原创)舌尖上的c++--相逢

引子


  前些时候,我在群里出了一道题目:将变参的类型连接在一起作为字符串并返回出来,要求只用函数实现,不能借助于结构体实现。用结构体来实现比较简单:


template<typename... Args> struct Connect;

template< typename First, typename... Rest>
struct Connect<First, Rest...>
{
static string GetName()
{
return typeid(First).name() + string(" ") + Connect<Rest...>::GetName();
}
};

template<typename Last> struct Connect<Last>
{
static string GetName()
{
return typeid(Last).name();
}
};

测试代码:


auto str = Connect<int, double>::GetName();
//str为int double

如果改成函数的话,试着这样写:


template<typename T>
string GetNameofTypes()
{
return typeid(T).name();
}

template<typename T, typename... Rest>
string GetNameofTypes()
{
string str += GetNameofTypes<T>() + " " + GetNameofTypes<Rest...>();

return str;
}

很遗憾,这样是编译不过的,因为编译器不知道选择哪个。有两个方法可以解决这个问题,这里主要来介绍通过逗号表达式来解决这个问题。

逗号表达式和变参的相逢

  前段时间播放的舌尖上的中国第二季中有一集为相逢,当南北不同风味的普通食材放到一起时,立刻化腐朽为神奇变成难得的美味了,这正是食材相逢组合而成的天作之合。那么在c++中,不同的特性相逢在一起又是什么滋味呢?一定很奇妙。下面来看看舌尖上的c++中两个普通食材吧。

食材一:逗号表达式

  我们知道逗号表达式会按顺序执行逗号前面的表达式,比如:
d = (a = b, c);

  这个表达式会按顺序执行的:a先会被赋值b,接着d会被赋值c。

食材二:可变参数模板

c++11的可变参数模板增强了模板功能,在c++11之前,类模板和函数模板只能含有固定数量的模板参数,现在c++11中的新特性可变参数模板允许模板定义中包含0到任意个模板参数。可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,可变参数模板声明时需要在typename或class后面带上省略号“...”。
省略号(...)的作用有两个

  • 声明一个参数包,这个参数包中可以包含0到任意个模板参数;

  • 在模板定义的右边,可以将参数包展开成一个一个独立的参数。

逗号表达式和变参的相逢

  来看看逗号表达式和变参的相逢会产生什么奇妙的效果。


template <class T>
void printarg(T t)
{
cout << t << endl;
}

template <class ...Args>
void expand(Args... args)
{
int arr[] = {(printarg(args), 0)...};
  cout<<sizeof(arr)<<endl;
}

测试代码:


expand(1,2,3,4);
//将输出1 2 3 4

  上面的例子将分别打印出1,2,3,4,将可变参数模板就地展开了。是的,是通过一个没有引用的数组来就地展开的,看到这种写法不要惊讶,这就是它们相逢产生的神奇效果,独有的味道。
  我们来看看这种奇妙的效果是如何产生的:

  我们知道逗号表达式会按顺序执行逗号前面的表达式,比如:
  d = (a = b, c);

  这个表达式会按顺序执行的:a先会被赋值b,接着d会被赋值c。
  expand函数中的逗号表达式:(printarg(args),
0),也是按照这个执行顺序,是先执行printarg(args),再得到逗号表达式的结果0。同时还用到了c++11的另外一个特性:初始化列表,通过初始化列表来初始化一个变长数组,
{(printarg(args), 0)...}将会展开成((printarg(arg1),0),
(printarg(arg2),0),printarg(arg3),0), etc... );最终会创建一个元素值都为0的数组int
arr[sizeof...(Args)],由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

  再回过头来说说本文开头提到的那个问题,通过函数将变参类型作为字符串返回出来。通过逗号表达式可以很轻松完成这个任务:


template<typename T>
string GetName()
{
return typeid(T).name();
}

template<typename... Rest>
string GetNameofTypes()
{
string str;
int arr[] = { (str +=" "+ GetName<Rest>(), 0)... };
   cout<<sizeof(arr)<<endl;
return str;
}

//测试代码:
auto s = GetNameofTypes<int, double, char>();
//s将为 int double char

  尝了上面的逗号表达式和可变参数模板的相逢产生奇妙的味道,一定还在回味之中,意犹未尽吧。我还没说其实还有一样食材没说呢,如果将逗号表达式和这种食材相逢在一起便又是另外一种独特的味道了。再来看看另外一种食材吧。

食材三:decltype表达式类型推断

  C++11新增了decltype关键字,用来在编译时推导出一个表达式的类型。它的语法格式如下:
decltype(exp)
  其中exp表示一个表达式(expression)。
它可以用来推导表达式标示符和表达式的类型,比如:


const int bar();
int i;
struct A { double x; };
const A* a = new A();
decltype(bar()) x2; // 类型为const int
decltype(i) x3; // 类型为int
decltype(a->x) x4; // 类型为double
decltype((a->x)) x5; // 类型为const double&

  我们一般用decltype来推断函数的返回类型,和auto结合起来,组成一种返回值类型后置的语法,比如下面的例子:


template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u)
{
return t + u;
}

  为什么要返回类型后置呢?因为返回类型要依赖于模板参数,如果将decltype(t +
u)放到函数的前面则无法编译通过,因为在定义函数返回值的时候,模板参数变量都还不存在呢。所以就借助auto来将返回类型占位住,等decltype推导之后再给auto初始化,从而获取了函数的返回值。

逗号表达式和decltype的相逢

  来看看逗号表达式和decltype的相逢又会产生什么奇妙的效果。比如有这样一个需求,我需要在编译期判断某个类是否存在void Reserve(int
i)函数。


template<typename T>
struct Has_Reserve_Method
{
private:
typedef std::true_type True;
typedef std::false_type False;

template<typename U> static auto Check(int) -> decltype(std::declval<U>().Reserve

(0), True());

template<typename> static False Check(...);

public:
enum
{
value = std::is_same<decltype(Check<T>(0)), True>::value
};
};

struct AA
{
void Reserve(int i)
{
cout << "ok" << endl;
}
};

测试代码:
if (Has_Reserve_Method<AA>::value)
cout << "ok" << endl;

//将输出OK

  这里利用了SFINAE特性,它的全称是Substitution failure is not an
error,匹配失败并不是错误,意思是用函数模板匹配规则来判断类型的某个属性是否存在,也就是说SFINAE可以作为一种编译期的不完整内省方法。


template<typename U> static auto Check(int) -> decltype(std::declval<U>().Reserve(0), True());
template<typename> static False Check(...);

  这两行代码是关键,当Check匹配不上时,返回False;当匹配上之后,通过逗号表达式返回True,这样外面就可以根据True和False来检查是否存在该函数了。

  怎么样这道菜的味道也相当好吧。其实还有很多c++特性的相逢产生的奇妙味道我们还没有发现,正等待着我们去发现呢。

后记

  由于typeid可能会丢失一些类型信息,要获取准确的类型名称还需要做一些专门的处理,这里为了简单起见,忽略了typeid获取类型名可能不准确的影响。另外,有童孩说逗号表达式来展开变参的代码可读性很差,这里我也不推荐大家在实际的代码中也这样写,还是老老实实的用结构体来展开变参吧,其实通过函数来展开变参(不用逗号表达式)还有种写法:


string GetNameofMsgType()
{
return "";
}

template <typename First>
string GetNameofMsgType()
{
return std::string(typeid(First).name());
}

template <typename First, typename Second, typename... Args>
string GetNameofMsgType()
{
return GetNameofMsgType<First>() + " " + GetNameofMsgType<Second, Args...>();
}

如果你觉得这篇文章对你有用,可以点一下推荐,谢谢。

c++11 boost技术交流群:296561497,欢迎大家来交流技术。

(原创)舌尖上的c++--相逢,布布扣,bubuko.com

时间: 2024-10-27 00:20:07

(原创)舌尖上的c++--相逢的相关文章

舌尖上的安卓(android触控事件机制学习笔记录)

对于一个"我们从来不生产代码,我们只是大自然代码的搬运工"的码农来说.对android的触控机制一直是模棱两可的状态,特别是当要求一些自定义的控件和androide的自带控件(比如ViewPager,ListView,ScrollView)高度嵌套在一起使用时. 花了点时间梳理了下,做个笔记.对于一个触控的事件从用户输入到传递到Actigvity到最外层的Viewgroup在到子View,中间过程还可能穿插多个Viewgroup,android在ViewGroup提供了3个方法来控制流

舌尖上的 Data Miner

谨以此文纪念过去两年的幸福时光~~~ 按下Shift-Alt-s, 这个来到DM最熟练的组合键,不用看跳出的对话框,直接按下Enter键.资深Data Miner提交Job都是这么帅的.Miner一边美滋滋的看着提交Job的圆圈,一圈一圈的转,一边在另一个屏幕上,浏览cnbate.对于上万行代码经验的Miner,他们的Code一般不会在提交30秒以内出现错误,那些语法错误通常是新手才会犯的.Miner的code错误一般会在编译一分钟以后才出现,更高级的是在运行中才会失败.遇到这种bug,Mine

舌尖上的新年

舌尖上的新年 在家过春节,享受着人神共享的美食 ! 有一大桌美食,吃好.喝好.玩儿好,才是硬道理!

舌尖上的程序猿

<舌尖上的程序员> 码完代码,他起身关上电脑,用滚烫的开水为自己泡制一碗腾着热气的老坛酸菜面.中国的程序猿更偏爱拉上窗帘,在黑暗中享受这独特的美食.这是现代工业给一天辛苦劳作的人最好的馈赠.南方一带生长的程序猿尽管在京城多年,但仍口味清淡,他们往往不加料包,由脸颊自然淌下的热泪补充恰当的盐分.他们相信,用这样的方式,可以抹平思考着如今是不是过去想要的未来而带来的大部分忧伤-小宋的父亲在年轻的时候也是从爷爷手里接收了祖传的代码,只是令人吃惊的是,到了小宋这一代,非常多东西都遗失了,可是程序猿苦逼

舌尖上的硬件: 看烤箱里的移动芯片商

1让我们换个地方思考吧,比如说厨房? “ARM.三星.NVIDIA.TSMC,这些厂商都是怎样的角色?”,“三星的猎户座和NVIDIA的Tegra 3都号称基于ARM的Cortex-A9,同样出处的它们为什么看起来却是完全不同的样子,性能也表现各异呢?”,“ARM到底是干什么的?三星又是干什么的?它们之间有着什么关系和联系啊?” 上面这些问题,相信曾经在很多读者的脑海中出现过.ARM是干什么的?三星和NVIDIA又是干什么的?这样的问题虽然并不会影响我们购买手机或者其他半导体产品,但求知欲的作祟

舌尖上的硬件: 厨房中探秘图形渲染

1食物?画卷?食物的画卷 在今后的<舌尖上的硬件>系列当中,我们将会继续保持这样的视角,以各种美味的组合和制作过程来为您展现不同的显卡/半导体芯片技术细节,五味的搭配因何而鲜美诱人.图形处理过程是怎样的精妙.食物为何会让我们大快朵颐.芯片的性能究竟由哪些要素决定.这个世界到底蕴藏了多少和谐与调和的艺术,小小厨房中所发生的一切将会成为我们带您认识电脑世界的载体.最终,我们将会一起探寻.发现并赞美这个世界的本质.如果一路上能够有您相伴,我们感激不尽. 信守承诺,带着一颗探索并感悟世界的心,我们和新

【康康说道】舌尖上的产品

"康康说道"此版块为我公司新开辟的版块,由我公司产品经理杨伟康独家解密医疗软件产品的各种奥秘之处. [杨伟康 简介]1987年生人.2010年开始进入医疗软件行业.2010年在江苏华招网期间进行医药招投标软件的开发:2011年至 今在南京一丹软件有限公司担任项目经理. 产品经理职位,期间负责EMR.CSSD,现接着负责体检软件的发, 开发期间多次与院方前线工作者进行需求对接,开发目标就是把自身作为终端用户.立志将自己负责的项目做到用户满意度最高. <舌尖上的中国>上 说:&

舌尖上的硬件:CPU/GPU芯片制造解析(高清)(组图)

一沙一世界,一树一菩提,我们这个世界的深邃全部蕴藏于一个个普通的平凡当中.小小的厨房所容纳的不仅仅是人们对味道的情感,更有推动整个世界前进的动力.要想理解我们的世界,有的时候只需要细细品味一下我们所喜爱的美食即可.正因为此,我们才规划了<舌尖上的硬件>这样一个系列栏目.通过对美食的品味和体会,我们可以更好地理解许多硬件相关的原理.内涵甚至是趣闻,我们所需要为此准备的,其实仅仅是一颗平和的心而已. 在上一期的<舌尖上的硬件>栏目中,我们第一次接触到了隐藏在食物背后的其与半导体业界的神

廣式典粤甜品加盟 舌尖上的护肤品

廣式典粤甜品加盟,舌尖上的护肤品.如今甜品成为生活当中备受喜爱的美食,吃甜品可以使人心情愉悦,在学习中遇到困难.工作中遇到烦心事,可以来些甜品放松一下自己,廣式典粤甜品店是您不错的选择. 廣式典粤甜品店招牌姜撞奶,姜撞奶具有祛寒行血.养颜美容功效,吃的同时更注重身体健康,更加健康.更加好吃,让您身心都感到放松,我们还有双皮奶.西米露.鲜芋圆.桃胶等各种美食甜品. 廣式典粤甜品店已经率先提出健康生活的理念,把健康作为基本的要素,融入到产品当中,集美味.健康于一身,是廣式典粤独有的优势. 廣式典粤甜