17

Context: To our surprise, MSVC with C++20 mode and two-phase compliance enabled accepts the following code:

template<class T>
class X
{
    friend int foo<X>(X x);

    int a = 10;
};

template <class T>
int foo(T t)
{
    return t.a;
}

int main()
{
    return foo(X<float>{});
}

This compiles/links and returns 10 in MSVC and gcc (but clang barks): https://godbolt.org/z/98cd7v7a6.

Those who know about two-phase lookup will find that this looks pretty crazy - we can somehow befriend a template specialization before the corresponding template is even declared. Looks and sounds very wrong. And indeed, for earlier C++ versions, all compilers would immediately reject it: https://godbolt.org/z/M1733EPd5

But it appears that the following wording in C++20 paves the way for this (or at least makes it not outright ill-formed from the start): https://timsong-cpp.github.io/cppwp/n4861/temp.names#2.sentence-4 (emphasis mine)

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.

Now, this leads to a whole host of... well, absurd corner cases. Note that outside of friend declarations you'd have to begin a template specialization with template<>, but that is not the case in friend declarations. Predictably, compilers cannot even remotely agree on what is now legal and what is not:

Befriend... godbolt MSVC gcc clang
...declaration of specialization of non-existent function template inside a class, call it https://godbolt.org/z/9bczs6nde
...declaration of specialization of non-existent function template inside an uninstantiated class template https://godbolt.org/z/461h774sz
...declaration of specialization of non-existent function template inside a class template, call it (note https://timsong-cpp.github.io/cppwp/n4868/temp.inject) https://godbolt.org/z/fK6s9aj5G
...declaration of specialization of non-existent function template specialization in class, provide function template definition, call it https://godbolt.org/z/Mevr4EWzG
...declaration of specialization of non-existent function template specialization in class template, provide function template definition, call it https://godbolt.org/z/d9s9hWsa7
...definition of specialization of previously declared function template inside a class, call it https://godbolt.org/z/ebrhvGrM4 ICE
...definition of specialization of previously declared function template inside a class template, call it https://godbolt.org/z/bsjKxWYco
...definition (!) of specialization of non-existent (!!) function template inside a class template and call it afterwards (!!!) https://godbolt.org/z/PnP6oKrrM

In short, wtf.

Question: Does the C++20 standard currently have a clear and consistent stance on these points:

  1. Is it legal for friend declarations to refer to a not-previously-declared function template? Does this depend on whether we are inside a template/involve dependent types?

  2. Is it ever (or always?) legal to define a specialization of a function template via a friend declaration? (See again https://godbolt.org/z/bTaYraP6j, https://godbolt.org/z/Y7qf6PPxY - some compilers state it's illegal but then also randomly accept it)

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • @Afshin FWIW, the standard appears to more or less acknowledge what you say in this note: https://timsong-cpp.github.io/cppwp/n4861/temp.names#2.sentence-2. But I have no clear concept of how ADL applies to this whole situation around `friend`, and it appears that the compilers do not either. – Max Langhof Aug 03 '22 at 11:19
  • 1
    Not an answer to the question, but the reason for the changed lookup rule was that - equally absurd - a totally unrelated local foo template would trigger an ADL search to find the one actually used. But without the local foo `foo – BoP Aug 03 '22 at 11:30
  • _https://timsong-cpp.github.io/cppwp/n4861/temp.names#2.sentence-4 ... Cursory search points to this paper being the origin of that: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1787r6.html_ A document [Generated on 2020-04-03](https://timsong-cpp.github.io/cppwp/n4861/) got its wording from an October 23, 2020 paper? Where «A name is also considered to refer to a template if it» is being removed? Seriously? – Language Lawyer Aug 03 '22 at 13:13
  • @LanguageLawyer I admit it was *very* cursory. Removed that part from the question. – Max Langhof Aug 03 '22 at 14:08
  • According to the answer in the Clang issue https://github.com/llvm/llvm-project/issues/46865, a declaration of a template named `foo` prior to the `friend` declaration of the specialization is required even in C++20 mode. (Note that the issue had been raised much later than the relevant change to the standard draft was made, see https://github.com/cplusplus/draft/commit/75516dac895d8a9ab7a925b8400989910e3b08ff). So that would mean Clang is correct to reject all of these cases. I am not sure how to get to this conclusion though. – user17732522 Aug 03 '22 at 16:02

2 Answers2

7

It’s true that there are no parsing difficulties here: C++20 does give the right meaning to the <, and template argument lists are parsed independently of the template declaration (which is how function template overloading and ADL can work).

Next, C++20 as published doesn’t say much in general about the process of declaration matching, leaving a number of questions open about which declarations refer to the same entity (validly or no). As such, it doesn’t really address the friend-before-declaration case, although its [namespace.memdef]/3 says

If the name in a friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier, the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace.

which implies that there is some sort of lookup performed, including in the template-id case, that could fail.

I say “as published” in the above because many of the fixes in the already-mentioned P1787R6 were considered to be Defect Reports (read: retroactive). I don’t recall an issue on specifically this subject, but the changes in that paper to [decl.meaning] to specify the special lookup process for declared names might best be considered retroactive as well.

As for defining a specialization, the only thing that any recent standard version says is in [temp.expl.spec]/1:

An explicit specialization […] can be declared by a declaration introduced by template<>; […]

This is unfortunately vague, but it’s not unreasonable to take as an implication that there is no other means of defining an explicit specialization, including via a friend declaration. Certainly that’s the intent: specializations aren’t found by name lookup, so there’s no point in making them hidden friends.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
5

I haven't checked C++20, but I think the latest draft standard says that the declaration of friend int foo<X>(X x); is ill-formed .

[dcl.meaning]/2:

If the declaration is a friend declaration:

  • If the id-expression E in the declarator-id of the declarator is a qualified-id or a template-id:
    • [...]
    • The declarator shall correspond to one or more declarations found by the lookup

In friend int foo<X>(X x);, the id-expression in the declarator-id is foo<X>. It is a template-id, so it is required to have a preceding declaration.

(For the reference, foo is looked up from the definition point ([temp.res.general]/1), because it is not a dependent name ([temp.dep.general]/2).)

cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • 2
    The quoted passage originates from https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1787r6.html. Unfortunately I wasn't really able to determine whether the same restriction was already present in some other way in C++20 or whether this is a newly-added (maybe as a resolution of one of the listed issues?). – user17732522 Aug 04 '22 at 09:13