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)