C++11新特性复习与速记3

标签: C 11 变长模板
20人阅读 评论(0) 收藏 举报
分类:

    这一节主要看一个比较晦涩的特性,变长模板

    C++11标准库中提供了一个tuple模板,通过它我们可以声明任意多个不同类型的元素的集合。例如

    std::tuple<int, char, std::string> collections;

    该collections变量可以容纳int, char, std::string三种类型的数据。当然我们也可以更为简单的利用C++11的make_tuple模板函数来创造一个tuple模板类型。这里学习一下tuple的实现方式。

1. 变长模板的语法,模板参数包和函数参数包

    template<typename ... Elements> class tuple;

    可以看到,我们在标识符Elements之前使用了省略号(...)来表示该参数是变长的。在C++11中,Elements被称为是一个“模板参数包(template parameter pack)”。

    一个模板参数包在模板推导时会被认为是模板的单个参数(虽然它可以打包任意数量的实参)。为了使用模板参数包,我们需要使用一个名为“包拓展”的表达式来完成解包。例如:

    template<typename T1, template T2> class B{};

    template<typename ... A> class TemplateClass : private B<A ... >{};

    这里我们为类模板TemplateClass声明了一个参数包A,而使用该参数包A...则是在TemplateClass的私有基类B<A...>中。当然,这里的疑问是B模板总是接受两个参数,倘若我们声明TemplateClass<X, Y, Z>必定会导致模板推导的错误。那么如何才能使模板能够接受任意多个的模板参数并均能实例化呢?

    实际上,在C++11中,实例化tuple模板的方式就给出了一种使用模板参数包的答案。其思路是使用数学归纳法,转化为计算机能够实现的手段则是递归。

    简化的tuple实现大致类似这样:

template<typename ... Elements> class tuple;

template<typename Head, typename ... Tail>
class tuple<Head, Tail...> : private tuple<Tail ...>{//递归的偏特化定义
    Head head;
};

template<> class tuple<> {};//边界条件

    设想我们实例化一个形如tuple<double, int, char>的类型时,会引起基类的递归构造,这样的递归直到tuple的参数包为0个的时候会结束。还原过程,编译期将从tuple<>出发,构造出tuple<char>,继而构造出tuple<int, char>,tuple<double, int, char>类型。

    当然了,变长的函数参数也可以声明为函数参数包(function parameter pack)。例如:

    template<typename ... T> void func(T ... args);

    C++11标准要求函数参数包必须唯一 且是函数的最后一个参数。可以看一下这个Printf的例子:

void Printf(const char* s) {
	while (*s) {
		if (*s == '%' && *++s != '%') {
			throw runtime_error("invalid format string: missing arguments");
		}
		cout << *s++;
	}
}

template<typename T, typename ...Args>
void Printf(const char *s, T value, Args...args) {
	while (*s) {
		if (*s == '%' && *++s != '%') {
			cout << value;
			return Printf(++s, args...);
		}
		cout << *s++;
	}
	throw runtime_error("extra arguments provided to Printf");
}

2. 进阶

    在上面TemplateClass的例子中,我们看见参数包在类的基类描述列表中进行了展开。而按照C++11的标准,参数包可以在下面7个位置进行展开:

表达式
初始化列表,使用{}进行列表初始化的地方
基类描述列表
类成员初始化列表
模板参数列表
通用属性列表
lambda函数的捕捉列表

    对于包拓展而言,其解包与其声明的形式息息相关。比如声明了Arg为参数包,那么我们可以使用Arg&& ... 这样的包拓展表达式,其解包后等价于Arg1&&, Arg2&&, ... , ArgN&&(设Arg1是参数包的第一个参数而ArgN是最后一个)。

    再看这个例子:

template<typename ... A> class T : privateB<A> ... {};

template<typename ... A> class T : private B<A ... > {};

    倘若我们实例化T<X, Y>,对于前者,会被解包为class T<X, Y> : private B<X>, private B<Y> {}; 而后者会被解包为class T<X, Y> : private B<X, Y>{}。

    类似的情况也会发生在函数声明上面。

template<typename ... T>
void DummyWrapper(T...t) {}

template<typename T>
T print(T t) {
	cout << t;
	return t;
}

template<typename ... T>
void Print(T...t) {
	cout << sizeof...(t) << endl;
	DummyWrapper(print(t)...);
}

    这个例子中DummyWrapper(print(t)...)的包拓展会被解包为print(Arg1),print(Arg2),... ,print(ArgN)。但如果我们测试Print("a", "b", "c"),我用g++编译得到的输出结果并非a, b, c,而是倒序的c, b, a。

    可以使用sizeof...操作符来计算参数包中的参数个数。

    最后是一个与完美转发结合的例子,Build函数看起来复杂,理解其原理还是很好读懂的。

struct A {
	A() {}
	A(const A& a) {
		cout << "A Copy Constructed " << __func__ << endl;
	}
	A(A && a) {
		cout << "A Move Constructed " << __func__ << endl;
	}

	void Func() {
		cout << "A" << endl;
	}
};

struct B {
	B() {}
	B(const B& b) {
		cout << "B Copy Constructed " << __func__ << endl;
	}
	B(B && b) {
		cout << "B Move Constructed " << __func__ << endl;
	}

	void Func() {
		cout << "B" << endl;
	}
};

template<typename ... T> struct MultyTypes;//声明
template<typename First, typename ...T>
struct MultyTypes<First, T...> : public MultyTypes<T...> {//递归偏特化
	First first;
	MultyTypes<First, T...>(First f, T...t) :
		first(f), MultyTypes<T...>(t...) {}
};

template<> struct MultyTypes<> {};//边界条件

template<template <typename ...> class VariadicType, typename ...Args>
VariadicType<Args...> Build(Args&& ... args) {//转发
	return VariadicType<Args...>(std::forward<Args>(args)...);
}

int main()
{
	A a;
	B b;

	Build<MultyTypes>(b, a);//在构造的时候不会调用任何的拷贝构造函数或者移动构造函数,\
							构造之后的类型只包含了对之前定义的变量a和b的引用

	system("pause");
	return 0;
}

查看评论

C++11新特性复习与速记(2)

书接上文,一些很已经熟悉了的特性不做赘述。一、auto无法带走cv限制符    在C++标准中,const和volatile被称为“cv限制符,cv-qualifier”,它们分别代表了变量的两种不同...
  • saasanken
  • saasanken
  • 2018-04-16 01:41:52
  • 8

c++11新特性总结

1、类型与变量相关 1.1、nullptr: 取代了NULL,专用于空指针 1.2、constexpr: 近似const, 可以修饰变量,也可以修饰函数, 修饰变量如: const...
  • u010246947
  • u010246947
  • 2017-09-03 15:50:56
  • 1771

深入理解C++11:C++11新特性解析与应用源代码

  • 2016年04月28日 20:06
  • 25KB
  • 下载

C++:C++11新特性详解(1)

前言:虽然目前没有编译器能够完全实现C++11,但这并不意味着我们不需要了解,学习它。深入学习C++11,你会发现这根本就是一门新的语言,它解决了c++98中许多遗留下来的问题。早晚会有一天,C++1...
  • linwh8
  • linwh8
  • 2016-06-05 23:28:49
  • 13247

深入理解C++11:C++11新特性解析与应用

2.11模板函数的默认模板参数1、C++98中仅模板类支持默认参数,模板函数不支持默认参数,但C++11中模板类和模板函数均支持默认参数 2、模板类的默认参数,对C++98和C++11中,需遵循“从...
  • yagerfgcs
  • yagerfgcs
  • 2017-03-22 13:57:57
  • 968

深入理解C++11:C++11新特性解析与应用(高清扫描)

  • 2014年11月26日 10:19
  • 55.66MB
  • 下载

C++11 新特性总结

http://www.cnblogs.com/George1994/p/6684989.html 前言 转载请注明出处,感谢! C++11 的新特性 1 变量和...
  • zdy0_2004
  • zdy0_2004
  • 2017-04-09 21:24:20
  • 4320

c++11的10个新特性

C++11标准由国际标准化组织(ISO)和国际电工委员会(IEC)旗下的C++标准委员会(ISO/IEC JTC1/SC22/WG21)于2011年8月12日公布[2]  ,并于2011年9月出版。...
  • whatday
  • whatday
  • 2016-01-13 11:38:04
  • 1250

深入理解C++11:C++11新特性解析与应用-- Michael Wong.pdf.pdf

  • 2015年09月17日 23:20
  • 33.02MB
  • 下载

深入理解C++11:C++11新特性解析与应用 pdf

  • 2017年06月02日 16:16
  • 57.66MB
  • 下载
    个人资料
    持之以恒
    等级:
    访问量: 341
    积分: 93
    排名: 122万+
    文章存档