Question: Why do functions disabled by C++20 `requires` clauses still cause ill-formed type errors?
Working with C++20's requires
statements, I've noticed that using requires
to selectively disable a function definition breaks if a type in that function would be ill-formed -- even though the function is not enabled.
The simplest example I've found for this is anything with a T&
where T
may be void
, for example:
template <typename T> struct maybe_void { maybe_void() requires(std::is_void_v<T>) = default; maybe_void(const T& v) requires(!std::is_void_v<T>) {} explicit maybe_void(int) {}; }; auto test() -> void { auto v = maybe_void<void>(42); // error -- sees 'const void&' constructor, even though it's disabled }
All major compilers agree that this is an error:
gcc-trunk
:<source>:12:33: required from here <source>:7:5: error: forming reference to void 7 | maybe_void(const T& v) requires(!std::is_void_v<T>) {} | ^~~~~~~~~~
clang-trunk
:<source>:7:23: error: cannot form a reference to 'void' maybe_void(const T& v) requires(!std::is_void_v<T>) {} ^ <source>:12:14: note: in instantiation of template class 'maybe_void<void>' requested here auto v = maybe_void<void>(42); ^
msvc-v19-latest
:<source>(7): error C2182: 'v': illegal use of type 'void' <source>(12): note: see reference to class template instantiation 'maybe_void<void>' being compiled
It was my understanding that requires
was meant to work in cases like the above, but the various compilers seem to suggest otherwise. Why doesn't this work?
Note:
One possible workaround is to change the constructor to a constrained template
instead:
template <typename U> explicit maybe_void(const U& v) requires(!std::is_void_v<U> && std::same_as<T,U>);
Although this works as a workaround, it doesn't explain why the requires
clause doesn't prevent the otherwise disabled function from triggering the ill-formed issue in the first place.