为什么在将尾随 return 类型与调用具有相同名称的函数的 decltype 一起使用时,模板实例化中会发生无限递归?

Why does infinite recursion in template instantiation happen when using trailing return type with decltype that calls the function with same name?

看这段代码:

#include <utility>

template <typename T>
class A
{
public:
    A(...) {}
};

template <typename T>
auto Func(A<T> &&a)
  -> void
{
}

template <typename T>
auto Func(A<T>  &a)
  -> decltype( Func<T>(std::move(a)) )
    { return ( Func<T>(std::move(a)) ); }

int main()
{
    Func<int>(5);
}

第二个重载请求 decltype 应该 return 第一个重载的“return 类型”(即 void)。

但是当我尝试用 GCC 编译这段代码时,它说模板实例化深度超过最大值:

main.cpp: In substitution of 'template<class T> decltype (Func<T>(std::move(a))) Func(A<T>&) [with T = int]':
main.cpp:17:23:   recursively required by substitution of 'template<class T> decltype (Func<T>(std::move(a))) Func(A<T>&) [with T = int]'
main.cpp:17:23:   required by substitution of 'template<class T> decltype (Func<T>(std::move(a))) Func(A<T>&) [with T = int]'
main.cpp:22:14:   required from here
main.cpp:17:33: fatal error: template instantiation depth exceeds maximum of 900 (use '-ftemplate-depth=' to increase the maximum)
   17 |   -> decltype( Func<T>(std::move(a)) )
      |                        ~~~~~~~~~^~~

coliru

当我尝试用 CLang 编译这段代码时,它 崩溃 :

#0 0x00007f0d4efc7eea llvm::sys::PrintStackTrace(llvm::raw_ostream&) (/usr/lib/x86_64-linux-gnu/libLLVM-5.0.so.1+0x7fbeea)
#1 0x00007f0d4efc606e llvm::sys::RunSignalHandlers() (/usr/lib/x86_64-linux-gnu/libLLVM-5.0.so.1+0x7fa06e)
#2 0x00007f0d4efc61bc (/usr/lib/x86_64-linux-gnu/libLLVM-5.0.so.1+0x7fa1bc)
#3 0x00007f0d51d9d390 __restore_rt (/lib/x86_64-linux-gnu/libpthread.so.0+0x11390)
#4 0x00007f0d4ef6311e llvm::FoldingSetNodeID::ComputeHash() const (/usr/lib/x86_64-linux-gnu/libLLVM-5.0.so.1+0x79711e)
#5 0x00007f0d4ef6318a llvm::FoldingSetBase::FindNodeOrInsertPos(llvm::FoldingSetNodeID const&, void*&) (/usr/lib/x86_64-linux-gnu/libLLVM-5.0.so.1+0x79718a)
#6 0x0000000001838633 clang::ASTContext::getLValueReferenceType(clang::QualType, bool) const (/usr/lib/llvm-5.0/bin/clang+0x1838633)
#7 0x00000000013dee9e (/usr/lib/llvm-5.0/bin/clang+0x13dee9e)
#8 0x00000000013e0998 clang::Sema::DeduceTemplateArguments(clang::FunctionTemplateDecl*, clang::TemplateArgumentListInfo*, llvm::ArrayRef<clang::Expr*>, clang::FunctionDecl*&, clang::sema::TemplateDeductionInfo&, bool, llvm::function_ref<bool (llvm::ArrayRef<clang::QualType>)>) (/usr/lib/llvm-5.0/bin/clang+0x13e0998)
#9 0x000000000131d398 clang::Sema::AddTemplateOverloadCandidate(clang::FunctionTemplateDecl*, clang::DeclAccessPair, clang::TemplateArgumentListInfo*, llvm::ArrayRef<clang::Expr*>, clang::OverloadCandidateSet&, bool, bool) (/usr/lib/llvm-5.0/bin/clang+0x131d398)
#10 0x000000000131e0d5 (/usr/lib/llvm-5.0/bin/clang+0x131e0d5)
#11 0x000000000131e22b clang::Sema::AddOverloadedCallCandidates(clang::UnresolvedLookupExpr*, llvm::ArrayRef<clang::Expr*>, clang::OverloadCandidateSet&, bool) (/usr/lib/llvm-5.0/bin/clang+0x131e22b)
#12 0x000000000131e3da clang::Sema::buildOverloadedCallSet(clang::Scope*, clang::Expr*, clang::UnresolvedLookupExpr*, llvm::MutableArrayRef<clang::Expr*>, clang::SourceLocation, clang::OverloadCandidateSet*, clang::ActionResult<clang::Expr*, true>*) (/usr/lib/llvm-5.0/bin/clang+0x131e3da)
#13 0x000000000132a6a3 clang::Sema::BuildOverloadedCallExpr(clang::Scope*, clang::Expr*, clang::UnresolvedLookupExpr*, clang::SourceLocation, llvm::MutableArrayRef<clang::Expr*>, clang::SourceLocation, clang::Expr*, bool, bool) (/usr/lib/llvm-5.0/bin/clang+0x132a6a3)
#14 0x00000000011d1e04 clang::Sema::ActOnCallExpr(clang::Scope*, clang::Expr*, clang::SourceLocation, llvm::MutableArrayRef<clang::Expr*>, clang::SourceLocation, clang::Expr*, bool) (/usr/lib/llvm-5.0/bin/clang+0x11d1e04)
#15 0x000000000140679c (/usr/lib/llvm-5.0/bin/clang+0x140679c)
#16 0x00000000013f5b2e (/usr/lib/llvm-5.0/bin/clang+0x13f5b2e)
#17 0x00000000013ff848 (/usr/lib/llvm-5.0/bin/clang+0x13ff848)
...

coliru

这段代码有什么问题?我该如何解决?

故事

我真正想要的是制作一个对 A<T> &&A<T> & 参数具有相同实现的函数。我想实现一个重载,在第二个重载中我只想将调用重定向到第一个重载。如果我的代码无效,我该如何使用其他方法?

在你的例子中实际上是触发无限递归的尾随return类型

  -> decltype(Func<T>(std::move(a)))

您可以在 this post.

中详细了解为什么会发生这种情况

如果您使用的是 C++14 和更高版本,只需将其忽略即可,它应该可以工作:Try it here!

您可以添加一个可选的模板参数来指向使用右值引用作为重载的函数,以避免无限递归。

#include <utility>

template <typename T>
class A
{
public:
    A(...) {}
};

template <typename T, typename... Args>
auto Func(A<T> &&a)
  -> void
{
}

template <typename T>
auto Func(A<T> &a)
  -> decltype( Func<T, void>(std::move(a)) )
    { return ( Func<T, void>(std::move(a)) ); }

int main()
{
    A<int> a;
    Func<int>(a);
    Func<int>(A<int>());
}

也适用于 C++11