Assume the following code:
#include <iostream>
template<typename T>
struct Link
{
Link(T&& val) : val(std::forward<T>(val)) {}
T val;
};
template<typename T>
std::ostream& operator<<(std::ostream& out, const Link<T>& link)
{
out << "Link(" << link.val << ")";
return out;
}
template<typename T>
auto MakeLink(T&& val) -> Link<T>
{
return {std::forward<T>(val)};
}
namespace Utils {
template<typename Any>
constexpr auto RemoveLinks(const Any& any) -> const Any&
{
return any;
}
template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))
{
return RemoveLinks(link.val);
}
} /* Utils */
int main()
{
int k = 10;
auto link = MakeLink(MakeLink(k));
std::cout << link << std::endl;
std::cout << Utils::RemoveLinks(link) << std::endl;
}
For some reason I can't understand, it generates the following compilation errors with g++-4.8
:
/home/allan/Codes/expr.cpp: In instantiation of ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = int&; decltype (Utils::RemoveLinks(link.val)) = const int&]’:
/home/allan/Codes/expr.cpp:88:32: required from ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = Link<int&>; decltype (Utils::RemoveLinks(link.val)) = const int&]’
/home/allan/Codes/expr.cpp:100:41: required from here
/home/allan/Codes/expr.cpp:88:32: error: invalid initialization of reference of type ‘const Link<int&>&’ from expression of type ‘const int’
return RemoveLinks(link.val);
^
/home/allan/Codes/expr.cpp:89:1: error: body of constexpr function ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = int&; decltype (Utils::RemoveLinks(link.val)) = const Link<int&>&]’ not a return-statement
}
^
/home/allan/Codes/expr.cpp: In function ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = int&; decltype (Utils::RemoveLinks(link.val)) = const int&]’:
/home/allan/Codes/expr.cpp:89:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
while clang 3.3 gives:
test.cc:34:12: error: reference to type 'const Link<int &>' could not bind to an lvalue of type 'const int'
return RemoveLinks(link.val);
^~~~~~~~~~~~~~~~~~~~~
test.cc:46:25: note: in instantiation of function template specialization 'Utils::RemoveLinks<Link<int &> >' requested here
std::cout << Utils::RemoveLinks(link) << std::endl;
If, however, the namespace Utils
is removed, then it compiles without errors (both gcc and clang), and execution outputs:
Link(Link(10))
10
Why defining those template functions (RemoveLinks
) in a namespace causes such errors?
First, many compilers historically have very poor support for templates, so the use of templates can make code somewhat less portable. Second, almost all compilers produce confusing, unhelpful error messages when errors are detected in template code. This can make templates difficult to develop.
Some of the advantages of using templates are: Templates simplify the creation of documents. Templates can ease our workload and make us feel less stressed, and, at the same time, they increase efficiency. Templates increase the attention of the audience.
When a function template is first called for each type, the compiler creates an instantiation. Each instantiation is a version of the templated function specialized for the type. This instantiation will be called every time the function is used for the type.
main cannot be a function template; it must be a function.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With