第125讲 DirectX11Frame(5)

这一讲的内容和我们的框架有些偏,但是他实现基础实现的一部分,这一讲的主题应该叫巧用C++的异常机制配合模板元技巧将一定格式的字符串转换到指定类型的tuple中。


大家都知道字符串是没有类型的,或者说他是一种更为一般的泛型,凡是可以通过流序列化的对象即可转换为字符的形式,比如“123”是可以转换到double,同样可以转换到int等等。而C++的tuple却又是强类型的东西,这两者的区别可以是千差万别,而我们今天的主题是要将两者结合在一起。


std::tuple_element<0,std::tuple<…>>::type

std::tuple_element<1,std::tuple<…>>::type


上面的方式是我们获取tuple元素类型的方法。下面进入主题。


 

 

数据储存在文本中那么都是字符串,所以当需要处理数据的时候需要将字符串转换成相应的类型,这本来没什么,只需要知道类型即可转换,那么如何将一个字符串转换到指定类型的tuple中呢?因为把参数放进数据放进tuple中有很多好处,简单点说在操作lua函数中可以带来很大的便利,好吧,就算不在C++中使用lua,那么统一管理数据也是很好的,再比如在操纵函数过程中用来打包函数参数也是很好的.



 

 

假如有一个函数需要int,float,float的参数,而这些参数我们大概可能是从文件中读取,那么他们是字符串,通常一般大概都会这么做,知道参数类型,将字符串转换成相应的类型的数据,然后调用函数,问题来了,这样硬编码是不是真的很好呢?




void test(int i, double b){

std::cout <<i <<std::endl;

std::cout <<b <<std::endl;

}

double test(int i, double b,double c){

std::cout <<i <<std::endl;

std::cout <<b <<std::endl;

std::cout <<c <<std::endl;


return i*b*c;

}


 

 

 

 

typedef double(*F)(int, double, double);

F __f = test;

std::string str;

std::cin>>str;

ManualFun(__f, str);

// 使用输入的参数调用函数


 

 

 

如果我们想要达到上面的效果,我们通常会根据函数特性来根据参数类型把字符串转换到tuple,在将tuple解包得到我们要的函数执行所必须的参数即可,当然这些都在内部函数的调用中完成,所以比较简单,这里就不多说,下面我们来看看怎么将那个储存参数的tuple提取出来。

 

 

嗯,为什么说把储存参数的tuple提取出来需要拿出来说一下呢?因为tuple的操作都是使用了大量的模板,其实就是tuple就是一个跨越编译期和运行期的一个模板元应用,所以操作tuple不能像操作一般的容器对待,这是因为每一次操作tuple其实都是在操作一个临时的类型不同的对象。简单点说,你要检查tuple的每一个数据的类型都是一个模板的不同实例化:

 

 

 

 

 

std::tuple_element<0,std::tuple<…>>::type

std::tuple_element<1,std::tuple<…>>::type

 

 

同样是获取tuple中元素的类型,但是不同的元素就是使用tuple_element的不同实例化,也就是说std::tuple_element<0,std::tuple<…>>和 std::tuple_element<1,std::tuple<…>>就像大圣和白龙马一样没啥关系。而问题是我们想要让字符串转换到指定的tuple中我们就要知道tuple中每一个元素的类型。所以,这样一来,还是逃离不了使用模板元的节奏。

 

 

 

 

 

template<size_t index,size_t M,class T>

struct TypeConvert;


template<size_t index, size_t M, class … Args>

struct TypeConvert<index, M, std::tuple<Args…>>{

typedef typename std::tuple_element<index, std::tuple<Args…>>::type Type;

template<class T>

struct Apply{

Apply(T t) :mT(t){ }

inline
void apply(std::vector<MString>&

v)

{

MString str = v.front();

v.erase(v.begin());

auto tt = std::tuple_cat(mT, std::make_tuple(str.ToOtherType<Type>()));

TypeConvert<index + 1, M, std::tuple<Args…>>::Apply<decltype(tt)>(tt).apply(v);

}

T mT;

};

};


template<size_t M,class … Args>

struct TypeConvert<M,M,std::tuple<Args…>>{

typedef  

typename std::tuple_element<M-1, std::tuple<Args…>>::type Type;

template<class T>

struct Apply{

Apply(T t) :mT(t){ }

inline
void apply(std::vector<MString>&

v)

{

;

}

T mT;

};

};

template<class…Args>

std::tuple<Args…>&

Totuple(const std::string&

str, std::tuple<Args…>&

output){

MString Mstr = str;

std::vector<MString>v;

Mstr.split(” t”, v);

if (v.size() <sizeof…(Args)){


return output;

}

TypeConvert<0, sizeof…(Args), std::tuple<Args…>>::Apply<std::tuple<>>(std::tuple<>()).apply(v);


return output;

 

// 这里不是我们想要的结果

}


 

 

好吧,到现在为止,我们确实是将字符串的内容转换到tuple里面了,mT就是储存结果的,但是问题来了,我们该怎么获取这最后一个mT呢?

 

 

 

当然下面的使用方式是不可以的:

inline auto apply(std::vector<MString>&

v)->decltype(……………)

{


return mT;

}


 

 

就算通过各种技巧将返回类型给推导出来,那代码大概也是相当难看的,之所以不这么做,是因为可以很简单的获取我们想要的结果,那就是异常,只需要在递归终止时将结果当做异常抛出来即可:

 

 

 

 

 

inline auto apply(std::vector<MString>&

v)->decltype(……………)

{

throw mT;

}


 

 

然后在ToTuple函数中抓一下异常:

template<class…Args>

std::tuple<Args…>&

Totuple(const std::string&

str, std::tuple<Args…>&

output){

MString Mstr = str;

std::vector<MString>v;

Mstr.split(” t”, v);

// 使用空格来或者t来分隔字符串

 

 

 

// MString是自己写的一个字符串库,使用boost里的算法也能够完成这些操作

if (v.size() <sizeof…(Args)){


return output;

}

try{

TypeConvert<0, sizeof…(Args), std::tuple<Args…>>::Apply<std::tuple<>>(std::tuple<>()).apply(v);

}

catch (std::tuple<Args…>e){

output = std::forward<std::tuple<Args…>&

&

>(e);


return output;

 

// 这里就是我们想要的结果

}

catch (…){


return output;

// 如果得到的结果不是我们想要的

}

}


 

 

 

有木有觉得很巧妙呢?在对输出流操作符重载一下让他可以接受输入是不是更好呢?

template<class…Args>

inline std::istream&

operator>>(std::istream&

is, std::tuple<Args…>&

t){

std::string str;

std::getline(is, str);

if (str.empty())


return is;

t = Totuple(str, t);


return is;

}

 

 

 

 

 

现在可以这么来对tuple进行操作,还能够从文件中读取数据并保存在指定的类型中:


std::tuple<int,float,float>t;

Totuple(“10 20.3 30.5”,t);

 

std::cout <<t <<std::endl;

std::cin >>t;

std::cout <<t <<std::endl;

 

 

 


//============================================

回复D查看目录,回复数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第125讲 DirectX11Frame(5)

|

发表评论