I have the following MWE in C++20 with clang++ -std=c++2a
, in which I defined in-class unary -
operator and friend
-ed binary -
operator:
template<typename T>
class vec;
template<typename T>
vec<T> operator-(const vec<T>&, const vec<T>&);
template<typename T>
class vec {
public:
vec() {}
vec operator-() const { return vec(); }
friend vec operator-<>(const vec&, const vec&);
};
template<typename T>
vec<T> operator-(const vec<T>& lhs, const vec<T>& rhs) { return vec<T>(); }
int main()
{
vec<int> v;
return 0;
}
However, this results in the following error in C++17:
main.cpp:12:16: error: friends can only be classes or functions
friend vec operator-<>(const vec&, const vec&);
^
main.cpp:12:25: error: expected ';' at end of declaration list
friend vec operator-<>(const vec&, const vec&);
^
;
with Apple clang version 11.0.3 (clang-1103.0.32.59)
.
The error disappears when I remove the in-class unary operator, or when I use C++20 via -std=c++2a
.
What is causing this issue in C++17, and how does C++20 resolves this issue? Any help would be greatly appreciated!
This is due to the way name look-up proceeds inside class context. Look up for names inside friend declarator are looked-up as in any member declarators. The pertinent lookup rules that apply here are:
In all the cases listed in [basic.lookup.unqual], the scopes are searched for a declaration in the order listed in each of the respective categories; name lookup ends as soon as a declaration is found for the name. If no declaration is found, the program is ill-formed.
A name used in the definition of a class X outside of a complete-class context ([class.mem]) of X shall be declared in one of the following ways:
- before its use in class X or be a member of a base class of X ([class.member.lookup]), or
- [...]
- if X is a member of namespace N, or is a nested class of a class that is a member of N, or is a local class or a nested class within a local class of a function that is a member of N, before the definition of class X in namespace N or in one of N's enclosing namespaces.
Which means that:
names are first look up in the class scope, for member names;
if this previous look up fails, name are looked up in the enclosing namespace scope.
When the compiler find the name operator-
in the friend declaration it performs name look in the class context (incomplete). It finds the unary minus operator and stop there.
After that the compiler applies the following rule to determine if the name operator -
can be a template name C++17/[temp.name]/3
After name lookup finds that a name is a template-name or that an operator-function-id or a literal-operator-id refers to a set of overloaded functions any member of which is a function template, if this is followed by a <, the < is always taken as the delimiter of a template-argument-list and never as the less-than operator. [...]
Lookup did not find any template, so inside the friend declaration operator -
is not supposed to name a template. The compiler complains precisely at the <
token that follows this name, which is not supposed to be there.
A new C++20 rule makes the compiler more inclined to interpret that a name refers to template, C++20 standard/[temp.names]/2:
A name is considered to refer to a template if name lookup finds a template-name or an overload set that contains a function template. A name is also considered to refer to a template if it is an unqualified-id followed by a < and name lookup either finds one or more functions or finds nothing.
Name lookup in class vec
scope find a function name and this name is followed by a <
character, so this name refers to a template.
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