Up until C++20 standard of C++, when we wanted to define an out-of-class operator which uses some private members of a template class, we'd use a construct similar to this:
template <typename T>
class Foo;
template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs);
template <typename T>
class Foo {
public:
constexpr Foo(T k) : mK(k) {}
constexpr friend bool operator==<T>(T lhs, const Foo& rhs);
private:
T mK;
};
template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
return lhs == rhs.mK;
}
int main() {
return 1 == Foo<int>(1) ? 0 : 1;
}
Since C++20, however, we can omit the out-of-class declaration, thus also the forward declaration, so we can get away with just:
template <typename T>
class Foo {
public:
constexpr Foo(T k) : mK(k) {}
constexpr friend bool operator==<T>(T lhs, const Foo& rhs);
private:
T mK;
};
template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
return lhs == rhs.mK;
}
Demo
Now, my question is, what part of C++20 allows us to do so? And why wasn't this possible in earlier C++ standards?
I filed a bug report on gcc's bugzilla
What is the syntax of class template? Explanation: Syntax involves template keyword followed by list of parameters in angular brackets and then class declaration. As follows template <paramaters> class declaration; 2.
A class template is a template that is used to generate classes whereas a template class is a class that is produced by a template.
A class template definition can only appear once in any single translation unit. A class template must be defined before any use of a template class that requires the size of the class or refers to members of the class. In the following example, the class template Key is declared before it is defined.
GCC has a bug.
Name lookup is always performed for template names appearing before a <
, even when the name in question is the name being declared in a (friend, explicit specialization, or explicit instantiation) declaration.
Because the name operator==
in the friend declaration is an unqualified name and is subject to name lookup in a template, the two-phase name lookup rules apply. In this context, operator==
is not a dependent name (it's not part of a function call, so ADL does not apply), so the name is looked up and bound at the point where it appears (see [temp.nondep] paragraph 1). Your example is ill-formed because this name lookup finds no declaration of operator==
.
I would expect GCC is accepting this in C++20 mode due to P0846R0, which permits (for example) operator==<T>(a, b)
to be used in a template even if no prior declaration of operator==
as a template is visible.
Here's an even more interesting testcase:
template <typename T> struct Foo;
#ifdef WRONG_DECL
template <typename T> bool operator==(Foo<T> lhs, int); // #1
#endif
template <typename T> struct Foo {
friend bool operator==<T>(Foo<T> lhs, float); // #2
};
template <typename T> bool operator==(Foo<T> lhs, float); // #3
Foo<int> f;
With -DWRONG_DECL
, GCC and Clang agree that this program is ill-formed: unqualified lookup for the friend declaration #2, in the context of the template definition, finds the declaration #1, which doesn't match the instantiated friend of Foo<int>
. Declaration #3 is not even considered, because unqualified lookup in the template doesn't find it.
With -UWRONG_DECL
, GCC (in C++17 and earlier) and Clang agree that this program is ill-formed for a different reason: unqualified lookup for operator==
on line #2 finds nothing.
But with -UWRONG_DECL
, GCC in C++20 mode appears to decide that it's OK that unqualified lookup for operator==
in #2 fails (presumably due to P0846R0), and then appears to redo the lookup from the template instantiation context, now finding #3, in violation of the normal two-phase name lookup rule for templates.
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