我试图编写自己的元组,并编写了以下代码:
#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>());
}
所以现在我更困惑了。
最佳答案
在有效的版本中,我们将其称为版本 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
对此的重载:
template<typename ...types,int index>
auto& getHelper(myTuple<types...> &tupleObj,IntToType<index>)
{
return getHelper(tupleObj,IntToType<index-1>());
}
Demo
如果没有尾随返回类型,types...
传递给下一个递归调用的不再是签名的一部分,并且编译器不再在尝试实例化越来越多的东西时陷入困境。请注意,输出可能不是您所期望的,并且 Clang 对此给出了有用的警告:此版本没有选择元组的正确元素。它总是选择第一个(在本例中是 int
,因此会出现转换警告)。
您的另一个较小的示例 test
有效是因为递归不是签名的一部分。将主要重载更改为此,您会遇到相同的问题情况:
template<typename ...types,int index>
auto test(typelist<types...> &t,IntToType<index>) -> decltype(test(t,IntToType<index-1>())) {
std::cout<<"test called with "<<index<<std::endl;
return test(t,IntToType<index-1>());
}
Demo
请注意,其他是否 test
并不重要。重载是否返回一个值,因为编译器甚至还没有足够远来进行重载解析。
我个人会继续使用版本 A,因为它工作得很好。如果您愿意,您可以通过删除尾随返回类型来简化它:
template<typename firstType,typename ...types>
auto& getHelper(myTuple<firstType,types...> &tupleObj,IntToType<0>)
{
return tupleObj.scatter<firstType,types...>::elem;
}
template<typename firstType,typename ...types,int index>
auto& getHelper(myTuple<firstType,types...> &tupleObj,IntToType<index>)
{
return getHelper(static_cast<myTuple<types...>&>(tupleObj),IntToType<index-1>());
}
template<int index,typename ...types>
auto& get(myTuple<types...> &tupleObj)
{
return getHelper(tupleObj,IntToType<index>());
}
如果您想使用类似版本 B 的内容,您可以更改它以使用辅助函数删除每个递归调用中的第一个类型:
template<typename firstType, typename ...types>
auto myTupleWithoutFirstType(myTuple<firstType, types...>) -> myTuple<types...>;
template<typename ...types,int index>
auto getHelper(myTuple<types...> &tupleObj,IntToType<index>)
-> decltype(getHelper(static_cast<decltype(myTupleWithoutFirstType(myTuple<types...>{}))&>(tupleObj),IntToType<index-1>()))
{
return getHelper(static_cast<decltype(myTupleWithoutFirstType(myTuple<types...>{}))&>(tupleObj),IntToType<index-1>());
}
关于c++ - 即使存在带有结束条件的重载,带有模板参数包的递归函数也会不断调用自身,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76522426/