您的位置:首页 > 编程语言 > C语言/C++

我的C++实践(11):存放异类对象的元组类型

2016-07-29 00:00 507 查看
C++中的数组类型和容器类型都是存放同类型对象的,有一个标准库类型std::pair可以存放两个异类对象。借助模板,我们可以开发出存放异类对象的类型。
1、二元组类型: Duo<T1,T2>。存放两个异类对象的元组类型,类似于std::pair。设置和获取各个域的操作直接为Duo<T1,T2>的成员函数。make_duo函数用来方便地创建二元组对象,类似于make_pair。

//duo.hpp:存放两个异类对象的二元组类型
#ifndef DUO_HPP
#define DUO_HPP

template <typename T1, typename T2>
class Duo {
public:
typedef T1 Type1; // 用于访问第1个域的类型
typedef T2 Type2; // 用于访问第2个域的类型
enum { N = 2 };   // 域的个数
private:
T1 value1; // 第1个域的值
T2 value2; // 第2个域的值
public:
Duo() : value1(), value2() { //缺省构造函数
}
Duo (T1 const& a, T2 const& b)
: value1(a), value2(b) {
}

// 针对兼容类型的拷贝构造函数,可进行隐式类型转换
template <typename U1, typename U2>
Duo (Duo<U1,U2> const& d)
: value1(d.v1()), value2(d.v2()) {
}

// 针对兼容类型的赋值运算符,可进行隐式类型转换
template <typename U1, typename U2>
Duo<T1, T2>& operator = (Duo<U1,U2> const & d) {
value1 = d.value1;
value2 = d.value2;
return *this;
}

// 访问域的函数
T1& v1() { //因为返回非const引用,可以修改对象的value1成员,因此函数
//也必须是非const的
return value1;
}
T1 const& v1() const { //const版本
return value1;
}
T2& v2() {
return value2;
}
T2 const& v2() const {
return value2;
}
};

// 两个二元组的比较运算符(允许混合类型)
template <typename T1, typename T2,
typename U1, typename U2>
inline
bool operator==(Duo<T1,T2> const& d1, Duo<U1,U2> const& d2){
return d1.v1()==d2.v1() && d1.v2()==d2.v2(); //相等比较
}

template <typename T1, typename T2,
typename U1, typename U2>
inline
bool operator!=(Duo<T1,T2> const& d1, Duo<U1,U2> const& d2){
return !(d1==d2);  //不等比较,可利用上面的相等比较运算符
}

// 包装函数:用来创建和初始化二元组的函数
template <typename T1, typename T2>
inline
Duo<T1,T2> make_duo(T1 const & a, T2 const & b){
return Duo<T1,T2>(a,b);
}

#endif


//duotest.cpp:对duo的测试
#include "duo.hpp"
#include <iostream>

Duo<float,int> foo (){
return make_duo(42,42);
}

int main(){
if (foo() == make_duo(42,42.0)) {
std::cout<<"foo()==make_duo(42,42.0)"<<std::endl;
}
return 0;
}


有几点要注意:
(1)我们用typedef定义了所存放的两个类型,这样就可以方便地访问两个类型。
(2)我们提供了针对兼容类型的拷贝构造函数和赋值运算符,从而可进行隐式类型转换。
(3)访问域的函数有非const版本和const版本。若函数返回非const引用,可以修改对象的value1成员,因此函数也必须是非const的,以表示它可以修改对象的值。
(4)比较运算符允许对混合类型进行比较。make_duo是包装函数,用来方便地创建和初始化二元组。
2、一般的元组类型: 可递归Duo<A, Duo<B,C> >类型表示一般的元组类,它使用模板元编程技术通过对Duo进行模板递归来实现。DuoT<N,T>用来获取元组类T中第N个域的类型,DuoValue<N,T>用来设置或获取元组类T中第N个域的值,它们也都使用了模板元编程技术来实现,用了很多模板特化来结束递归。val<N>(d)用来方便地设置和获取元组d的第N个域的值。

//recduo.hpp:一般多元组类型,用模板元编程技术来实现(对Duo进行模板递归)
#ifndef REC_DUO_HPP
#define REC_DUO_HPP
#include "duo.hpp"     //普通的Duo二元组模板
#include "typeop.hpp"  //用TypeOp来确保返回需要的类型
/*
* Duo<A, Duo<B,C> >:用可递归Duo实现一般的多元组类型
*/
template <typename A, typename B, typename C>
class Duo<A, Duo<B,C> > { //对普通Duo的局部特化,用来递归地表示一般的多元组
public:
typedef A Type1;         // 第1个域的类型
typedef Duo<B,C> Type2;  // 可递归Duo类型:包含了其他各个域
enum { N = Duo<B,C>::N + 1 };  // 域的个数
private:
Type1 value1; // 第1个域的值
Type2 value2; // 可递归Duo对象:包含了其他的域
public:
Duo() : value1(), value2() { //缺省构造函数
}
Duo (Type1 const& a, Type2 const& b)
: value1(a), value2(b){
}

// 针对兼容类型的拷贝构造函数,可进行隐式类型转换
template <typename U1, typename U2>
Duo(Duo<U1,U2> const& d)
: value1(d.v1()), value2(d.v2()) {
}

// 针对兼容类型的赋值运算符,可进行隐式类型转换
template <typename U1, typename U2>
Duo<Type1, Type2>& operator=(Duo<U1,U2> const & d) {
value1 = d.value1;
value2 = d.value2;
return *this;
}

Type1& v1() { // 访问第1个域的函数
return value1;
}
Type1 const& v1() const { //const版本
return value1;
}

Type2& v2() { //访问可递归Duo对象
return value2;
}
Type2 const& v2() const {
return value2;
}
};
template <typename A>
class Duo<A,void> { //终止递归的局部特化:Duo中只有一个域
public:
typedef A    Type1;  // 第1个域的类型
typedef void Type2;  // 第2个域的类型,为void空类型
enum { N = 1 };  // 域的个数
private:
Type1 value1;  // 第1个域的值
public:

Duo() : value1() { //缺省构造函数
}
Duo (Type1 const & a)
: value1(a) {
}

// 访问域的函数
Type1& v1() {
return value1;
}
Type1 const& v1() const { //const版本
return value1;
}

void v2() { //因为只有一个域,故对第2个域不做任何事
}
void v2() const {
}
};

/*
* DuoT<N,T>:用来获取元组类T中第N个域的类型
* 元组类可以是普通的Duo(二元组),也可以是可递归Duo(一般的多元组)
*/
template <int N, typename T>
class DuoT { //基本模板,获取第n个域的类型
public:
typedef void ResultT; // 一般情况下返回void空类型
};

template <typename A, typename B>
class DuoT<1, Duo<A,B> > { //特化:返回普通Duo第1个域的类型
public:
typedef A ResultT;
};

template <typename A, typename B>
class DuoT<2, Duo<A,B> > { //特化:返回普通Duo第2个域的类型
public:
typedef B ResultT;
};

template <typename A, typename B, typename C>
class DuoT<1, Duo<A, Duo<B,C> > > { //特化:返回可递归Duo第1个域的类型
public:
typedef A ResultT;
};

template <typename A, typename B, typename C>
class DuoT<2, Duo<A, Duo<B,C> > > { //特化:返回可递归Duo第2个域的类型
public:
typedef B ResultT;
};

template <int N, typename A, typename B, typename C>
class DuoT<N, Duo<A, Duo<B,C> > > { //特化:返回可递归Duo第N(N>=3)个域的类型
public:
//它相当于第2个域(即Duo<B,C>)的第N-1个域的类型
typedef typename DuoT<N-1, Duo<B,C> >::ResultT ResultT;
};

/*
* DuoValue<N,T>:用来设置或者获取元组类T中第N个域的值
* 元组类可以是普通的Duo(二元组),也可以是可递归Duo(一般的多元组)
*/
template <int N, typename T>
class DuoValue{ //基本模板
public:
static void get(T&){ // 一般情况下并不返回值
}
static void get(T const&){ //static成员函数不能有const修饰符
}
};

template <typename A, typename B>
class DuoValue<1, Duo<A, B> > { //特化:设置或获取普通Duo第1个域的值
public:
static A& get(Duo<A, B>& d) { //返回非const引用,可获取域的值,也可设置域的值
//因此参数d也必须是变量型的(非const的)
//否则d加const会使d.v1()会返回const引用
return d.v1();
}
static A const& get(Duo<A, B> const& d){ //返回const引用,只获取域的值
return d.v1();
}
};

template <typename A, typename B>
class DuoValue<2, Duo<A, B> > { //特化:获取普通Duo第2个域的值
public:
static B& get(Duo<A, B>& d) {
return d.v2();
}
static B const& get(Duo<A, B> const& d){
return d.v2();
}
};

template <typename A, typename B, typename C>
class DuoValue<1, Duo<A, Duo<B,C> > > { //特化:获取可递归Duo第1个域的值
public:
static A& get(Duo<A, Duo<B,C> > &d) {
return d.v1();
}
static A const& get(Duo<A, Duo<B,C> > const &d) {
return d.v1();
}
};

template <typename A, typename B, typename C>
class DuoValue<2, Duo<A, Duo<B,C> > > { //特化:获取可递归Duo第2个域的值
public:
static B& get(Duo<A, Duo<B,C> > &d) {
return d.v2().v1();
}
static B const& get(Duo<A, Duo<B,C> > const &d) {
return d.v2().v1();
}
};
template <int N, typename A, typename B, typename C>
class DuoValue<N, Duo<A, Duo<B,C> > > { //特化:获取可递归Duo第N(N>=3)个域的值
public:
//用TypeOp确保返回类型是非const的引用类型:用到了DuoT模板
static typename TypeOp<typename DuoT<N-1, Duo<B,C>
>::ResultT>::RefT
get(Duo<A, Duo<B,C> >& d) {
//它相当于获取第2个域(即Duo<B,C>)的第N-1个域的值
return DuoValue<N-1, Duo<B,C> >::get(d.v2());
}

//用TypeOp确保返回类型是const引用类型
static typename TypeOp<typename DuoT<N-1, Duo<B,C>
>::ResultT>::RefConstT
get(Duo<A, Duo<B,C> > const &d) {
return DuoValue<N-1, Duo<B,C> >::get(d.v2());
}
};
//包装函数:用于方便地设置或获取元组类第N个域的值
//模板参数N要显式给出,A和B可通过演绎获得
template <int N, typename A, typename B>
inline typename TypeOp<typename DuoT<N, Duo<A, B>
>::ResultT>::RefT
val(Duo<A, B>& d){ //设置或者获取变量型duo的第N个值
return DuoValue<N, Duo<A, B> >::get(d);
}

template <int N, typename A, typename B>
inline typename TypeOp<typename DuoT<N, Duo<A, B>
>::ResultT>::RefConstT
val(Duo<A, B> const& d){ //只能获取常量型duo的第N个值
return DuoValue<N, Duo<A, B> >::get(d);
}

#endif


//recduotest.cpp:对用可递归Duo实现的一般多元组类进行测试

#include "recduo.hpp"
#include <iostream>

int main()
{
// 创建和使用一个简单的duo
Duo<bool,int> d;
std::cout << d.v1() << std::endl;
std::cout << val<1>(d) << std::endl;

// 创建和使用三元组
Duo<bool,Duo<int,float> > t;

val<1>(t) = true;
val<2>(t) = 42;
val<3>(t) = 0.2;

std::cout << val<1>(t) << std::endl;
std::cout << val<2>(t) << std::endl;
std::cout << val<3>(t) << std::endl;
return 0;
}


要注意的地方:
(1)非const版本的那个静态get(d)函数由于返回的是非const引用,通过这个引用可获取d中相应域的值,也可设置d中相应域的值,因此参数d也必须是非const的(即变量型的),否则如果d是const的,则不能为d中的域设置新值,另一方面它会使d.v1()会返回const引用,从而get(d)不能返回非const引用。d为const的那个get(d)函数返回const引用,只能用于获取相应域的值,但函数本身不能是const的,因为static成员函数不能同时又是const的。
(2)在特化版本DuoValue<N, Duo<A, Duo<B,C> > >中,我们用到了类型萃取技术中开发的TypeOp模板,这可确保返回类型是相应域类型的非const引用或const引用。
(3)val<N>(d)要提供两个版本,一个接受非const的引用参数d,可设置和获取d的第N个域的值,一个接受const引用参数d,只能获取d的第N个域的值,注意基于“引用型参数是否是const的”可以构成重载关系。
(4)元组类型Duo<A, Duo<B,C> >虽然理论上可以存放任意多个异类元素,但实际的编译器对模板嵌套深度是有限制的,因此这个元组类在实际应用时并不能存放太多个异类元素。
3、元素个数有限的元组类: 由于模板嵌套深度在实际中有限制,因此有时我们更愿意开发一个接口简单的、元素个数有限的元组类。Tuple<P1,P2,P3,P4,P5>就是这样一个元组类,它可存放1-5个域。也是用模板递归来实现的,Tuple继承自前面的可递归Duo类型,其中该Duo类型的域个数有限。

//tuple.hpp:能存放1-5个元素的元组类型
#ifndef TUPLE_HPP
#define TUPLE_HPP
#include "recduo.hpp"  //可递归Duo类型
#include "typeop.hpp"  //用TypeOp来确保返回需要的类型

class NullT { //代表无用类型
};

// 一般情况下,Tuple<>创建自“至少含有一个NullT的另一个Tuple<>“
template <typename P1,
typename P2 = NullT,
typename P3 = NullT,
typename P4 = NullT,
typename P5 = NullT>
class Tuple : public Duo<P1, typename Tuple<P2,P3,P4,P5,NullT>::BaseT> {
public:
//基类是一个可递归Duo类型
typedef Duo<P1, typename Tuple<P2,P3,P4,P5,NullT>::BaseT>
BaseT;

Tuple() {} //缺省构造函数
Tuple(typename TypeOp<P1>::RefConstT a1,
typename TypeOp<P2>::RefConstT a2,
typename TypeOp<P3>::RefConstT a3 = NullT(),
typename TypeOp<P4>::RefConstT a4 = NullT(),
typename TypeOp<P5>::RefConstT a5 = NullT())
: BaseT(a1, Tuple<P2,P3,P4,P5,NullT>(a2,a3,a4,a5)) {
}
};

// 结束递归的局部特化:Tuple有2个域,它派生自普通的Duo
template <typename P1, typename P2>
class Tuple<P1,P2,NullT,NullT,NullT> : public Duo<P1,P2> {
public:
typedef Duo<P1,P2> BaseT; //基类类型
Tuple() {}
Tuple(typename TypeOp<P1>::RefConstT a1,
typename TypeOp<P2>::RefConstT a2,
typename TypeOp<NullT>::RefConstT = NullT(),
typename TypeOp<NullT>::RefConstT = NullT(),
typename TypeOp<NullT>::RefConstT = NullT())
: BaseT(a1, a2) {
}
};

// 特化:Tuple只有1个域,它派生自只有一个域的Duo特化
template <typename P1>
class Tuple<P1,NullT,NullT,NullT,NullT> : public Duo<P1,void> {
public:
typedef Duo<P1,void> BaseT;
Tuple() {}
Tuple(typename TypeOp<P1>::RefConstT a1,
typename TypeOp<NullT>::RefConstT = NullT(),
typename TypeOp<NullT>::RefConstT = NullT(),
typename TypeOp<NullT>::RefConstT = NullT(),
typename TypeOp<NullT>::RefConstT = NullT())
: BaseT(a1) {
}
};

// 方便创建元组的make_tuple函数:1个实参的情况
template <typename T1>
inline
Tuple<T1> make_tuple(T1 const& a1){
return Tuple<T1>(a1);
}

// 方便创建元组的make_tuple函数:2个实参的情况
template <typename T1, typename T2>
inline
Tuple<T1,T2> make_tuple(T1 const& a1, T2 const& a2){
return Tuple<T1,T2>(a1,a2);
}

// // 方便创建元组的make_tuple函数:3个实参的情况
template <typename T1, typename T2, typename T3>
inline
Tuple<T1,T2,T3> make_tuple(T1 const& a1, T2 const& a2,
T3 const& a3){
return Tuple<T1,T2,T3>(a1,a2,a3);
}

// 方便创建元组的make_tuple函数:4个实参的情况
template <typename T1, typename T2, typename T3, typename T4>
inline
Tuple<T1,T2,T3,T4> make_tuple(T1 const &a1, T2 const &a2,
T3 const &a3, T4 const &a4){
return Tuple<T1,T2,T3,T4>(a1,a2,a3,a4);
}

// 方便创建元组的make_tuple函数:5个实参的情况
template <typename T1, typename T2, typename T3,
typename T4, typename T5>
inline
Tuple<T1,T2,T3,T4,T5> make_tuple(T1 const &a1, T2 const &a2,
T3 const &a3, T4 const &a4,
T5 const &a5){
return Tuple<T1,T2,T3,T4,T5>(a1,a2,a3,a4,a5);
}
#endif


//tupletest.cpp;对Tuple的测试
#include "tuple.hpp"
#include <iostream>

int main()
{
//创建和使用只有一个域的tuple
Tuple<int> t1;
val<1>(t1) += 42;
std::cout << t1.v1() << std::endl;

//创建和使用duo
Tuple<bool,int> t2;
std::cout << val<1>(t2) << ", ";
std::cout << t2.v1() << std::endl;

//创建和使用triple
Tuple<bool,int,double> t3;

val<1>(t3) = true; //设置各个域的值
val<2>(t3) = 42;
val<3>(t3) = 0.2;

std::cout << val<1>(t3) << ", ";
std::cout << val<2>(t3) << ", ";
std::cout << val<3>(t3) << std::endl;

t3 = make_tuple(false, 23, 13.13); //在创建时给定各个域的值

std::cout << val<1>(t3) << ", ";
std::cout << val<2>(t3) << ", ";
std::cout << val<3>(t3) << std::endl;

//创建和使用quadruple
Tuple<bool,int,float,double> t4(true,42,13,1.95583);
std::cout << val<4>(t4) << std::endl;
std::cout << t4.v2().v2().v2() << std::endl;
return 0;
}


有几点要注意:
(1)由于Tuple的域个数是可变的,因此我们声明了一个无用类型NullT来作模板参数的默认实参。这里并没有使用void类型,因为我们需要在构造函数中创建该类型的对象来作为函数形参的缺省值,而void类型不能创建对象。
(2)用typedef定义BaseT来表示它继承的可递归Duo类型。
(3)make_tuple用来方便地创建tuple元组。由于函数模板不能含有缺省模板实参,因此我们不能只写一个含有5个模板参数(通过给一些模板参数提供缺省实参)的make_tuple。如果想通过给函数的5个形参提供缺省调用实参来达到只编写一个make_tuple的愿望,这也不行,因为在演绎模板参数时,不会考虑缺省调用实参,这样就会导致使用缺省调用实参的模板参数演绎不出来,从而必须显式指定,使用起来不方便。总之,我们必须针对不同的域个数来定义相应的make_tuple。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: