In the following example the requires-expression of second f
-function overload has the type std::integral_constant<bool,true>
, which is implicitly convertible to bool
:
#include <type_traits>
struct S {
static constexpr bool valid = true;
};
template<typename T>
int f() { return 1; }
template<typename T>
int f() requires( std::bool_constant< T::valid >() ) { return 2; }
int main() {
return f<S>();
}
One can observe that GCC rejects the program due to the type is not precisely bool
, but Clang accepts, but selects the other overload int f() { return 1; }
. Demo: https://gcc.godbolt.org/z/nf65zrxoK
Which compiler is correct here?
CodePudding user response:
I believe GCC is correct—the type must be bool
exactly per [temp.constr.atomic]/3 (note that E
here is std::bool_constant< T::valid >()
):
To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true. If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, the program is ill-formed, no diagnostic required. [ Example:
template<typename T> concept C = sizeof(T) == 4 && !true; // requires atomic constraints sizeof(T) == 4 and !true template<typename T> struct S { constexpr operator bool() const { return true; } }; template<typename T> requires (S<T>{}) void f(T); // #1 void f(int); // #2 void g() { f(0); // error: expression S<int>{} does not have type bool } // while checking satisfaction of deduced arguments of #1; // call is ill-formed even though #2 is a better match
— end example ]