https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90734
Bug ID: 90734 Summary: [concepts] Pre-normalization substitution into constraints of templated function breaks subsumption Product: gcc Version: 10.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: Casey at Carter dot net Target Milestone: --- Created attachment 46447 --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=46447&action=edit Repro Compiling this program: template <bool B> inline constexpr bool bool_ = B; #if defined(WORKAROUND) template<class T, class U> concept bool Same_impl = __is_same_as(T, U); #else template <class T, class U> concept bool Same_impl = bool_<__is_same_as(T, U)>; #endif template<class T, class U> concept bool Same = Same_impl<T, U> && Same_impl<U, T>; template<class T> concept bool Foo = Same<const T&, const T&>; template<class T> concept bool Bar = Foo<T> && Same<T, T>; template<class T> struct S1 { // overload set incorrectly is ambiguous (should resolve to second overload) static constexpr bool f() requires Foo<T> { return false; } static constexpr bool f() requires Bar<T> { return true; } }; template<class T> struct S2 { // overload set incorrectly is not ambiguous (resolves to third overload) static constexpr bool f() requires Foo<T> { return false; } static constexpr bool f() requires Bar<T> { return false; } static constexpr bool f() requires bool_<true> && true { return true; } }; template<class T> concept bool can_f = requires { T::f(); }; int main() { static_assert(Foo<int>); static_assert(Bar<int>); static_assert(can_f<S1<int>>); // Fails static_assert(S1<int>::f()); // Bogus error static_assert(!can_f<S2<int>>); // Fails #ifndef WORKAROUND static_assert(S2<int>::f()); // Bogus non-error #endif } with "-std=c++2a -fconcepts" produces diagnostics: /home/casey/casey/Desktop/repro.cpp: In function ‘int main()’: /home/casey/casey/Desktop/repro.cpp:43:19: error: static assertion failed 43 | static_assert(can_f<S1<int>>); // Fails | ^~~~~~~~~~~~~~ /home/casey/casey/Desktop/repro.cpp:44:30: error: call of overloaded ‘f()’ is ambiguous 44 | static_assert(S1<int>::f()); // Bogus error | ^ /home/casey/casey/Desktop/repro.cpp:24:27: note: candidate: ‘static constexpr bool S1<T>::f() requires Foo<T> [with T = int]’ 24 | static constexpr bool f() requires Foo<T> { return false; } | ^ /home/casey/casey/Desktop/repro.cpp:25:27: note: candidate: ‘static constexpr bool S1<T>::f() requires Bar<T> [with T = int]’ 25 | static constexpr bool f() requires Bar<T> { return true; } | ^ /home/casey/casey/Desktop/repro.cpp:46:19: error: static assertion failed 46 | static_assert(!can_f<S2<int>>); // Fails | ^~~~~~~~~~~~~~~ when it should diagnose only the static_assert on line 48. Bar<T> subsumes Foo<T>, so S1<int>::f should be unambiguous. Conversely, Neither Bar<T> nor bool_<true> && true subsumes the other, so S2<int>::f should be ambiguous. The compiler's disagreement with both of these facts suggests premature substitution replacing bool_<__is_same_as(T, T)> and bool_<__is_same_as(const T&, const T&)> with bool_<true> *before* determination of subsumption in overload resolution. That replacement would result in Foo<T> being replaced with bool_<true> && bool_<true>, and Bar<T> being replaced with bool_<true> && bool_<true> && bool_<true> && bool_<true> which *would* produce the observed behavior during overload resolution.