c++ - 即使存在带有结束条件的重载,带有模板参数包的递归函数也会不断调用自身

标签 c++ templates

我试图编写自己的元组,并编写了以下代码:

#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) 删除每个递归调用中的第一个类型。哪里tupleObjmyTuple<firstType,types...> 。所以firstType被扔掉了。

在失败的版本中,我们称之为版本 B,您没有删除任何内容:您直接传递 tupleObj其中包含所有类型。

这很重要,因为函数的签名包含传递给下一个递归调用的类型,位于尾随返回类型( -> decltype(...) )中。在版本 A 中,没有问题,因为在某些时候,参数包 types 中不再剩下任何类型。当编译器尝试实例化getHelperfirstType 没有剩余类型,它失败,停止实例化事物,然后解析调用。在版本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>());
}

Demo

如果您想使用类似版本 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>());
}

Demo

关于c++ - 即使存在带有结束条件的重载,带有模板参数包的递归函数也会不断调用自身,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76522426/

相关文章:

C++ 二维 shared_ptr 数组用抽象多态类型初始化

C++ 代码风格 - 创建对象的最佳位置

c++ - C++ 中的命名空间类模板继承

c++ - 奇怪的模板编译错误: is this a g++ bug, a Clang bug, or…?

c++ - libQt5Core.so : undefined reference to `__cxa_throw_bad_array_new_length@CXXABI_1. 3.8

c++ - 如何在需要时互换不同的运算符(operator)?

xaml - 如何在 XAML 中创建主布局模板

c++ - 如何创建一个接口(interface)与 double 匹配但模板可以专门化的类?

c++ 从宏到模板

c++ - 低 RAM 使用率 + 频繁分配/释放导致 Linux 换出其他程序