我试图编写自己的元组,并写了下面的代码:
#include<iostream>
template<typename T>
struct Holder{
T elem;
};
template<typename firstType, typename ...types>
struct scatter : public Holder<firstType>{};
template<typename firstType,typename ...types>
struct myTuple: public scatter<firstType,types...>,public myTuple<types...>{};
template<typename firstType>
struct myTuple<firstType> : public scatter<firstType>{};
template<int>
struct IntToType{};
/* these work ok */
template<typename firstType,typename ...types>
auto getHelper(myTuple<firstType,types...> &tupleObj,IntToType<0>)
-> decltype((tupleObj.scatter<firstType,types...>::elem))
{
return tupleObj.scatter<firstType,types...>::elem;
}
template<typename firstType,typename ...types,int index>
auto getHelper(myTuple<firstType,types...> &tupleObj,IntToType<index>)
-> decltype(getHelper(static_cast<myTuple<types...>&>(tupleObj),IntToType<index-1>()))
{
return getHelper(static_cast<myTuple<types...>&>(tupleObj),IntToType<index-1>());
}
/* these don't work, the first getHelper function doesn't work as an exit */
//template<typename ...types>
//auto getHelper(myTuple<types...> &tupleObj,IntToType<0>)
//-> decltype((tupleObj.scatter<types...>::elem))
//{
// return tupleObj.scatter<types...>::elem;
//}
//
//template<typename ...types,int index>
//auto getHelper(myTuple<types...> &tupleObj,IntToType<index>)
//-> decltype(getHelper(tupleObj,IntToType<index-1>()))
//{
// return getHelper(tupleObj,IntToType<index-1>());
//}
template<int index,typename ...types>
auto get(myTuple<types...> &tupleObj)
-> decltype(getHelper(tupleObj,IntToType<index>()))
{
return getHelper(tupleObj,IntToType<index>());
}
int main(){
myTuple<int,int,float,double> obj;
get<0>(obj) = 2;
get<3>(obj) = 3.5;
std::cout<<get<0>(obj)<<std::endl;
std::cout<<get<3>(obj)<<std::endl;
}
上面的代码运行良好,但是当我用注解中的模板替换两个getHelper
函数模板时,递归模板(第二个getHelper
)会继续调用自己,编译失败。
有什么问题吗?
我也尝试了下面的例子,它工作得很好:
#include <iostream>
template<int>
struct IntToType{};
template<typename ...types>
struct typelist{};
template<typename ...types>
void test(typelist<types...> &t,IntToType<0>){
std::cout<<"test called with 0"<<std::endl;
}
template<typename ...types,int index>
void test(typelist<types...> &t,IntToType<index>){
std::cout<<"test called with "<<index<<std::endl;
test(t,IntToType<index-1>());
}
int main(){
typelist<int,int,char> obj{};
test(obj,IntToType<2>());
}
所以现在我更困惑了
2条答案
按热度按时间z2acfund1#
在有效的版本中,我们称之为版本A,您通过执行
static_cast<myTuple<types...>&>(tupleObj)
删除每个递归调用中的第一个类型,其中tupleObj
是myTuple<firstType,types...>
。所以firstType
被抛弃了。在失败的版本中,我们称之为版本B,您没有删除任何内容:你直接传递
tupleObj
,它包含了所有类型。这很重要,因为函数的签名包含传递给下一个递归调用的类型,在尾部返回类型(
-> decltype(...)
)中。在版本A中,没有问题,因为在某个时候,在参数包types
中没有更多的类型,当编译器试图示例化getHelper
时,没有留下firstType
的类型,它失败了,停止示例化,然后解析调用。在版本B中,参数包types
中总是有一些东西,因此编译器一直尝试使用相同的模板参数示例化getHelper
,除了索引,索引每次都会减少。**模板参数推导是在重载解析之前进行的。**在版本B中,重载解析虽然会用
IntToType<0>
选取正确的重载,但由于模板参数推导卡在了重载解析中,所以无法到达:它尝试示例化无限数量的getHelper
函数。为了让你相信这一点,在版本B中,将主
getHelper
重载改为:Demo
如果没有尾部返回类型,传递给下一个递归调用的
types...
就不再是签名的一部分,编译器也不再会在尝试示例化越来越多的东西时陷入困境。请注意,输出可能不是您所期望的,Clang对此给出了有用的警告:该版本没有挑选元组的正确元素。它总是选择第一个(在本例中是int
,因此转换警告)。另一个较小的
test
示例可以工作,因为递归不是签名的一部分。将主重载更改为此,您将得到相同的问题情况:Demo
请注意,另一个
test
重载是否返回值并不重要,因为同样,编译器甚至不足以进行重载解析。我个人会继续使用版本A,因为它工作得很好。如果你愿意,你可以通过删除尾部返回类型来简化它:
Demo
如果你想使用类似于版本B的东西,你可以修改它,用一个helper函数删除每个递归调用中的第一个类型:
Demo
gev0vcfq2#
在工作代码中,有两个重载版本的
getHelper
:1.第一个版本采用
myTuple<firstType, types...>
和IntToType<0>
作为参数。它返回tupleObj.scatter<firstType, types...>::elem
的类型,这是元组中第一个元素的类型。这个版本作为递归的退出条件。1.第二个版本采用
myTuple<firstType, types...>
和IntToType<index>
作为参数。它递归调用getHelper
,并使用index-1
和元组中的其余类型。它返回递归调用的结果。此版本处理索引不为零的情况。第一个版本的模板参数推导工作正常,因为类型
myTuple<firstType, types...>
在函数签名中显式指定。但是,在注解代码中,getHelper的第一个版本试图直接从tupleObj推导模板参数,这会导致一个问题。由于tupleObj的类型是myTuple<types...>
,其中types是一个参数包,因此推导失败,因为无法从参数包推导出模板参数。在getHelper的第二个版本中,递归调用
getHelper(tupleObj, IntToType<index-1>())
也无法匹配模板参数,因为模板参数不能从tupleObj推导出来。