8

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!

Jay Lee
  • 1,684
  • 1
  • 15
  • 27
  • 1
    @MooingDuck: That syntax makes *all* specializations friends. – Davis Herring May 06 '20 at 18:34
  • Note that if you switch the in-class friend declaration with unary `-` definition (by swapping both lines), the code compiles (tested with gcc, clang, intel, and msvc). – Daniel Langr May 06 '20 at 18:49
  • 2
    Declaring the friend with `vec (::operator-<>) (...)` also make it compile successfully. – StoryTeller - Unslander Monica May 06 '20 at 18:51
  • @DanielLangr Ok, it does indeed work, and it's very strange... – Jay Lee May 06 '20 at 18:54
  • @StoryTeller-UnslanderMonica And that works magically as well! Is this a bug in a compiler, or does what I wrote previously leads to an undefined behavior? – Jay Lee May 06 '20 at 18:55
  • 1
    @StoryTeller-UnslanderMonica Even with `vec ::operator-<>(...)`. BTW, the problem happens not only for operators but named member functions as well: [live demo](https://godbolt.org/z/6dx2gm). – Daniel Langr May 06 '20 at 19:00
  • 3
    @DanielLangr - `vec ::operator-<>(...)` shouldn't work. Clang rightly considers it a nested name specifier. The parentheses strictly speaking, are required. I suspect it only works in your demo because the return type is `void`. C++ can be infuriating at times. – StoryTeller - Unslander Monica May 06 '20 at 19:02
  • 1
    I believe this is related question: [friend declaration of template specialization fails](https://stackoverflow.com/q/8514880/580083). – Daniel Langr May 06 '20 at 19:09
  • I really can't find anything that changed between C++17 and the current draft in this regard. I am not a C++ language-lawyer, but I believe that [temp.friend/1.1](http://eel.is/c++draft/temp.friend#1.1) applies here. Shortened quote: _A friend of a class template can be a specialization of a function template. For a friend function declaration that is not a template declaration: if the name of the friend is a qualified or unqualified template-id, the friend declaration refers to a specialization of a function template, otherwise..._. – Daniel Langr May 06 '20 at 20:10

1 Answers1

1

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:

  1. names are first look up in the class scope, for member names;

  2. 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.

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • I’m not at all sure that name lookup is performed in the normal way for such a declaration: certainly a friend function declaration can’t define a class member, so it doesn’t seem meaningful to say that it can find one from an *unqualified-id*. – Davis Herring May 09 '20 at 02:39
  • @DavisHerring OK I update the answer with the look up rules that I think apply here: unqualified name look up inside incomple class context. – Oliv May 09 '20 at 06:12
  • @DavisHerring I do not see any special treatment for friend declarator inside [basic.lookup.unqual]. – Oliv May 09 '20 at 06:25
  • First, [basic.lookup.unqual] is a disaster: it doesn’t even handle nested classes correctly. More importantly, a *declarator-id* is not in *general* subject to normal unqualified lookup: otherwise it would be impossible to overload anything since the name would mean the previous declaration. (This question may inform further development of [my paper](http://open-std.org/JTC1/SC22/WG21/docs/papers/2020/p1787r4.html) that tries to clarify all this.) – Davis Herring May 09 '20 at 16:31
  • @DavisHerring I based my reflection on the hypothesis that the paragraph inside '[temp.name]' described a first parsing pass that is run at each statement just to decide weither the token '<' means 'less' or 'start of template argument list', and weither what is before '<' is a 'declarator-id' or an 'id-expression'. I took this hypothesis because I think this is what it wass written in the paper that caused the introduction of the sentence in [temp.names] that I bolded, but I can't find anymore this paper and my memory is not that of a computer. – Oliv May 09 '20 at 16:48
  • @DavisHerring I am reading your paper, it is clearer. It make me think that what I actually did in my answer was finding a way to give sense to the standard, probably as some do with their holy book. – Oliv May 09 '20 at 16:58
  • You’re right that `<` interpretation is interleaved with name lookup, but since unqualified friend declarations are always namespace members, it’s surely wrong to use ordinary unqualified lookup to decide that interpretation (even if that seems to explain the version difference here). Unfortunately, you’re also right that divination is all too often involved—thus the paper, which I’m glad you find clearer. – Davis Herring May 09 '20 at 18:40