Consider this example (https://godbolt.org/z/hrE3YEzPd):
#include <cstdint>
class Foo {
template <typename Write>
void WriteNestedMessage(uint32_t field_number, Write write_message);
protected:
void Write();
};
template <typename Write>
void Foo::WriteNestedMessage(uint32_t field_number, Write write_message) {}
In this example:
We have an in-class template declaration method named WriteNestedMessage
. Its template argument Write
shadows the name of the protected Write
method.
We have an out-of-class template definition for WriteNestedMessage
whose template argument is also named Write
.
Clang accepts this code without complaint.
GCC complains:
<source>:12:53: error: 'Write' is not a type
12 | void Foo::WriteNestedMessage(uint32_t field_number, Write write_message) {}
| ^~~~~
<source>:12:6: error: no declaration matches 'void Foo::WriteNestedMessage(uint32_t, int)'
12 | void Foo::WriteNestedMessage(uint32_t field_number, Write write_message) {}
| ^~~
<source>:5:8: note: candidate is: 'template<class Write> void Foo::WriteNestedMessage(uint32_t, Write)'
5 | void WriteNestedMessage(uint32_t field_number, Write write_message);
| ^~~~~~~~~~~~~~~~~~
<source>:3:7: note: 'class Foo' defined here
3 | class Foo {
| ^~~
<source>:12:53: error: 'void Foo::Write()' is protected within this context
12 | void Foo::WriteNestedMessage(uint32_t field_number, Write write_message) {}
| ^~~~~
<source>:8:8: note: declared protected here
8 | void Write();
| ^~~~~
Compiler returned: 1
It clearly believes -- in the template definition -- that Write
is referring to the protected method, not the template parameter of the same name.
cppreference.com - Template parameters says a number of things, none of which directly apply, and are potentially contradictory:
In the definition of a member of a class template that appears outside of the class template definition, the name of a member of the class template hides the name of a template parameter of any enclosing class templates, but not a template parameter of the member if the member is a class or function template.
This suggests that Clang is correct, and Write
should refer to the template parameter, because we're defining an out-of-class function template.
In the definition of a member of a class template that appears outside of the namespace containing the class template definition, the name of a template parameter hides the name of a member of this namespace.
This also suggests that Clang is correct, though it might not apply due to the "outside of the namespace" part.
In the definition of a class template or in the definition of a member of such a template that appears outside of the template definition, for each non-dependent base class, if the name of the base class or the name of a member of the base class is the same as the name of a template parameter, the base class name or member name hides the template parameter name.
This suggest that GCC is correct, though it might not apply because it specifically refers to members of base classes (and not the enclosing class).
Clang is right.
https://timsong-cpp.github.io/cppwp/n4950/temp.local#7
Unqualified name lookup considers the template parameter scope of a template-declaration immediately after the outermost scope associated with the template declared (even if its parent scope does not contain the template-parameter-list).
[Note 1: The scope of a class template, including its non-dependent base classes ([temp.dep.type], [class.member.lookup]), is searched before its template parameter scope. — end note]
[Example 6:
struct B { }; namespace N { typedef void V; template<class T> struct A : B { typedef void C; void f(); template<class U> void g(U); }; } template<class V> void N::A<V>::f() { // N::V not considered here V v; // V is still the template parameter, not N::> V } template<class B> template<class C> void N::A<B>::g(C) { B b; // B is the base class, not the template parameter C c; // C is the template parameter, not A's C }
— end example]
Clang previously got this wrong but it was fixed in Clang 11:
https://github.com/llvm/llvm-project/commit/d1446017f3fdc2f6a9efba222008d20afa1e26cc
There is an open bug in GCC:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120255
Footnote: Name lookup was rewritten in the spec just after C++20:
C++20 language: https://timsong-cpp.github.io/cppwp/n4868/temp.local#7
C++23 language: https://timsong-cpp.github.io/cppwp/n4950/temp.local#7
(Thanks David and Richard for tracking this down!)