This fails to compile with -std=gnu++2a: template<typename T, typename U> constexpr bool same = false; template<typename T> constexpr bool same<T, T> = true; template<typename T, typename U> concept same_same = same<T, U> && same<U, T>; template<typename T> struct traits { using type = T; }; struct tag { }; template<typename T> struct category { }; template<typename T> requires requires { typename traits<T>::type; } struct category<T> { }; template<typename T> requires requires { typename traits<T>::type; } && same_same<typename traits<T>::type, tag> struct category<T> { using type = int; }; category<tag>::type t; ambig.cc:27:14: error: ambiguous template instantiation for 'struct category<tag>' 27 | category<tag>::type t; | ^~ ambig.cc:18:10: note: candidates are: 'template<class T> requires requires{typename traits<T>::type;} struct category<T> [with T = tag]' 18 | struct category<T> | ^~~~~~~~~~~ ambig.cc:24:10: note: 'template<class T> requires requires{typename traits<T>::type;} && (same_same<typename traits::type, tag>) struct category<T> [with T = tag]' 24 | struct category<T> | ^~~~~~~~~~~ ambig.cc:27:16: error: invalid use of incomplete type 'struct category<tag>' 27 | category<tag>::type t; | ^~~~ ambig.cc:13:10: note: declaration of 'struct category<tag>' 13 | struct category | ^~~~~~~~ It compiles OK if the subsumed constraint is a concept instead of a requires-expression, even though that should be equivalent: template<typename T, typename U> constexpr bool same = false; template<typename T> constexpr bool same<T, T> = true; template<typename T, typename U> concept same_same = same<T, U> && same<U, T>; template<typename T> struct traits { using type = T; }; template<typename T> concept traitsy = requires { typename traits<T>::type; }; struct tag { }; template<typename T> struct category { }; template<typename T> requires traitsy<T> struct category<T> { }; template<typename T> requires traitsy<T> && same_same<typename traits<T>::type, tag> struct category<T> { using type = int; }; category<tag>::type t;
Reduced: template<typename> concept nope = false; template<typename> concept sure_thing = true; template<typename T> struct category { }; template<typename T> requires (!nope<T>) struct category<T> { }; template<typename T> requires (!nope<T>) && sure_thing<T> struct category<T> { using type = T; }; category<int>::type t; ambig.cc:19:14: error: ambiguous template instantiation for 'struct category<int>' 19 | category<int>::type t; | ^~ ambig.cc:12:10: note: candidates are: 'template<class T> requires !(nope<T>) struct category<T> [with T = int]' 12 | struct category<T> | ^~~~~~~~~~~ ambig.cc:16:10: note: 'template<class T> requires !(nope<T>) && (sure_thing<T>) struct category<T> [with T = int]' 16 | struct category<T> | ^~~~~~~~~~~ ambig.cc:19:16: error: invalid use of incomplete type 'struct category<int>' 19 | category<int>::type t; | ^~~~ ambig.cc:8:10: note: declaration of 'struct category<int>' 8 | struct category | ^~~~~~~~ To make subsumption of (!E) work I need to add a concept for !E template<typename T> concept nope_nope = !nope<T>; and then use that: template<typename T> requires nope_nope<T> struct category<T> { }; template<typename T> requires nope_nope<T> && sure_thing<T> struct category<T> { using type = T; };
In C++20, atomic constraints are only equivalent if they come from the same lexical tokens. So yes, you need to add a concept.
*** Bug 95626 has been marked as a duplicate of this bug. ***