I made a macro for the "implies" operator. It works fine in general, but breaks on Clang when used in a concept or a requires-clause.
namespace detail
{
template <typename T>
concept BoolLike = requires(T &&t) {(T &&)t ? true : false;};
struct Implies
{
template <BoolLike T>
friend constexpr bool operator||(T &&lhs, Implies)
{
return !bool((T &&)lhs);
}
};
}
#define IMPLIES ||::detail::Implies{}||
template <typename T>
concept A = true IMPLIES true;
static_assert(A<int>);
Clang says:
<source>:19:18: error: atomic constraint must be of type 'bool' (found '::detail::Implies')
19 | concept A = true IMPLIES true;
| ^~~~~~~
<source>:16:19: note: expanded from macro 'IMPLIES'
16 | #define IMPLIES ||::detail::Implies{}||
| ^~~~~~~~~~~~~~~~~~~
While GCC and MSVC happily accept this code. Which compiler is correct?
Overloaded logical operators don't really work in constraint expressions (at the top level): the constraint normalization machinery turns expressions of the forms E1 || E2
and E1 && E2
into disjunctions and conjunctions, respectively, based purely on their syntactic form, without regard to whether there is a user-provided overload for the operator ([temp.constr.normal]).
In this particular example, this means that the normal form of A
's constraint-expression contains, among others, an atomic constraint formed from the expression ::detail::Implies{}
, which is never of type bool
.
To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. [...]
E
shall be a constant expression of typebool
.
Since this program never checks the invalid atomic constraint for satisfaction (because ||
short-circuits), it is ill-formed with no diagnostic required ([temp.res.general]/6.4):
The program is ill-formed, no diagnostic required, if
- any constraint-expression in the program, introduced or otherwise, has (in its normal form) an atomic constraint A where no satisfaction check of A could be well-formed and no satisfaction check of A is performed