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

C++ 模板元中巧用异常让字符串储存在指定类型的tuple中

2016-02-17 15:47 513 查看

std::tuple_element<0,std::tuple<...>>::type
std::tuple_element<1,std::tuple<...>>::type


数据储存在文本中那么都是字符串,所以当需要处理数据的时候需要将字符串转换成相应的类型,这本来没什么,只需要知道类型即可转换,那么如何将一个字符串转换到指定类型的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;
说了这些,其实是想说,异常有时候不仅仅只是标识错误的发生,他还能够帮我们解决很多事,我就不说以前就干过用异常来传递数据的事了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: