Search code examples
c++language-lawyer

Overloaded `&&`/`||` operators in concepts and requires-clauses


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.

run on gcc.godbolt.org

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?


Solution

  • 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.

    [temp.constr.atomic]/3:

    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 type bool.

    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