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.
CodePudding user response:
Well, MSVC is definitely wrong. It thinks the requires-expression is actually true. But [dcl.fct.def.delete]/2 says:
A program that refers to a deleted function implicitly or explicitly, other than to declare, it, is ill-formed.
[Note 1: This includes calling the function implicitly or explicitly and forming a pointer or pointer-to-member to the function. It applies even for references in expressions that are not potentially-evaluated. For an overload set, only the function selected by overload resolution is referenced. The implicit odr-use ([basic.def.odr]) of a virtual function does not, by itself, constitute a reference. — end note]
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 instantiate f<void>
, i.e., synthesize the definition of that specialization (and a definition is a declaration), rather than refer to it.