Reputation: 23701
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:
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?
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)
Upvotes: 17
Views: 362
Reputation: 40013
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.
Upvotes: 7
Reputation: 15918
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 .
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).)
Upvotes: 5