Consider a function template f<T> with the primary template deleted but some specializations defined. We can use a requires-expression to test if f<T> has been specialized:
template <typename T>
int f() = delete;
template <>
int f<int>() {
return 1;
}
template <typename T>
int g() {
if constexpr (requires { f<T>; }) {
return f<T>();
} else {
return -1;
}
}
int main() { return g<void>(); }
Version 1 (godbolt link), same as above: compiles on clang but not gcc or msvc.
Version 2 (godbolt link): replacing requires { f<T>; } with requires { f<T>(); } it now compiles on gcc as well.
Version 3 (godbolt link): replacing the inline requires-expression with a named concept, it now compiles on all three compilers.
I am curious which of these versions are well-formed C++20 code and which instances represent compiler bugs.
Well, MSVC is definitely wrong. It thinks the requires-expression is actually true. But [dcl.fct.def.delete]/2 says:
The expression
f<T>is an lvalue that refers to the deleted function, so it "refers" to it; this is ill-formed and it should be detected by the requires-expression.The only question is whether the ill-formedness is actually in the immediate context. The standard doesn't explain what "immediate context" means, but going off the commonly understood meaning, the issue is whether the ill-formedness is the instantiation of the deleted
f<void>(which would not be in the immediate context) or the reference to that deleted function (which would be in the immediate context). I'm inclined to go with Clang here: the instantiation itself is not ill-formed, because its effect is to instantiatef<void>, i.e., synthesize the definition of that specialization (and a definition is a declaration), rather than refer to it.