非终止地递归使用decltype

Non-terminating recursive use of decltype

本文关键字:decltype 递归 终止      更新时间:2023-10-16

由于无限编译时递归,以下代码的编译存在问题。Clang 3.6.0给出了一个关于递归模板实例化深度的错误,并且没有终止;而GCC 4.9.2保持沉默,也不会终止。

这是一个比我最初面对的更简单的例子,当然bar的第二次重载可以将int作为返回类型;在这种情况下,对main中的bar的调用选择第一个过载;正如我最初希望的那样。为什么decltype的应用程序不能解析为Foo专用的第一个过载?

Foo的第二个(默认)模板参数似乎隐藏了关于这里递归问题的更详细的消息。这在原来的上下文中是很有用的。

template <typename T, typename A = T>
struct Foo {};
template <typename T, typename A>
int bar(const Foo<T,A> &x) { return 0; }
template <typename T>
auto bar(const T &x)
 -> decltype(bar(Foo<T>{})){    
    return   bar(Foo<T>{});    
}
int main(int argc, char *argv[])
{
  Foo<char> f;    
  bar(f);
  return 0;
}

这失败是因为decltype表达式需要为这些参数计算出bar的返回类型,所以它需要实例化所有bar模板,这涉及到查看需要为这些参数计算出bar的返回类型的decltype表达式,所以它需要实例化所有bar模板…我相信你可以想象这最终会导致编译器崩溃。

在c++ 14中,我们可以省略后面的返回类型来避免所有这些工作。在c++ 11中,当TFoo时,我们可以禁用重载:
namespace detail {
    template <typename T>
    struct is_foo : std::false_type{};
    template <typename T, typename A>
    struct is_foo<Foo<T,A>> : std::true_type{};
}
template <typename T>
using is_foo = detail::is_foo<typename std::decay<T>::type>;
template <typename T, typename A>
int bar(const Foo<T,A> &x) { return 0; }
template <typename T, 
   typename = typename std::enable_if<!is_foo<T>::value>::type>
auto bar(const T &x)
 -> decltype(bar(Foo<T>{})){    
    Foo<T> b{};
    return bar(b);    
}