Assume the following code:
#include
template
struct Link
{
Link(T&& val) : val(std::forward(val)) {}
I tried compiling the sample code above with GCC 4.8.1, with clang and also Intel icpc and got the same error messages as you.
I am able to get it to successfully compile without trouble if I revise the signature of the template specialization from:
template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))
and make the return type as const. This might cause a compiler warning since the const there is meaningless, but that can be ignored. I tested it and it works fine for me with gcc, but not icpc or clang:
template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val)) const
I found the error message (with the original code) from Intel icpc to be the most informative:
code.cc(48): error: template instantiation resulted in unexpected function type of "auto (const Link<Link<int &>> &)->const int &
" (the meaning of a name may have changed since the template declaration -- the type of the template is "auto (const Link<T> &)->decltype((<expression>))
")
std::cout << Utils::RemoveLinks(link) << std::endl;
^
detected during instantiation of "Utils::RemoveLinks" based on template argument <Link<int &>>
at line 48
Unfortunately, the above answer is more of a workaround for gcc rather than an answer to your question. I'll update this if I have anything more / better to add.
EDIT
It appears that decltype(RemoveLinks(link.val)) is actually following the recursion so that it returns int& rather than Link.
EDIT #2
There have been reported bugs in LLVM about crashes caused by decltype recursion problems. It seems that this is definitely a bug of sorts, but one that seems to be present in multiple implementations of C++.
The problem can be fixed quite easily if you create a an alias for type T in the link struct and have decltype refer to the alias rather than to the return type. This will eliminate the recursion. As follows:
template<typename T>
struct Link
{
Link(T&& val) : val(std::forward<T>(val)) {}
using value_type = T;
T val;
};
And then the RemoveLinks signature is changed accordingly to refer to this alias:
template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(links.value_type)
This code successfully builds on all 3 compilers.
I will file some bug reports with the compilers to see if there's anything they can do about it.
Hope this helps.
This problem is a result of an issue with the point of declaration (1) combined with dependent name lookup (2).
(1) In the declaration
template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))
the name RemoveLinks
, or more precisely, this overload of RemoveLinks
, is only visible after the complete declarator according to [basic.scope.pdecl]/1. The trailing-return-type is part of the declarator as per [dcl.decl]/4. Also see this answer.
(2) In the expression RemoveLinks(link.val)
, the name RemoveLinks
is dependent as per [temp.dep]/1, as link.val
is dependent.
If we now look up how dependent names are resolved, we find [temp.dep.res]:
In resolving dependent names, names from the following sources are considered:
- Declarations that are visible at the point of definition of the template.
- Declarations from namespaces associated with the types of the function arguments both from the instantiation context and from the definition context.
The first bullet doesn't find the second overload of RemoveLinks
because of the point of declaration (1). The second one doesn't find the overload because the namespace Util
is not associated with any argument. This is why putting everything in the global namespace or in the namespace Util
works as expected (Live example).
For the same reason, using a qualified-id in the trailing-return-type (like -> decltype(Util::RemoveLinks(link.val))
doesn't help here.