Search code examples
c++c++20c++-conceptsrequires-clause

Implicit expression variations of a concept is not handled correctly?


Implicit expression variations of a requires-expression should meet the followings:

A requires expression that uses an expression that is non-modifying for some constant lvalue operand also implicitly requires additional variations of that expression that accept a non-constant lvalue or (possibly constant) rvalue for the given operand unless such an expression variation is explicitly required with differing semantics.

These implicit expression variations must meet the same semantic requirements of the declared expression. The extent to which an implementation validates the syntax of the variations is unspecified.

template<class T>
concept C = requires(T a, T b, const T c, const T d)
{
    c == d;           // expression #1: does not modify the operands
    a = std::move(b); // expression #2: modifies both operands
    a = c;            // expression #3: modifies the left operand `a`
};

// Expression #1 implicitly requires additional expression variations that
// meet the requirements for c == d (including non-modification),
// as if the following expressions had been declared as well:

// ------ const == const ------- ------ const == non-const ---
//                                         c  ==           b;
//            c == std::move(d);           c  == std::move(b);
// std::move(c) ==           d;  std::move(c) ==           b;
// std::move(c) == std::move(d); std::move(c) == std::move(b);

// -- non-const == const ------- -- non-const == non-const ---
//           a  ==           d;            a  ==           b;
//           a  == std::move(d);           a  == std::move(b);
// std::move(a) ==           d;  std::move(a) ==           b;
// std::move(a) == std::move(d); std::move(a) == std::move(b);

// Expression #3 implicitly requires additional expression variations that
// meet the requirements for a = c
// (including non-modification of the second operand),
// as if the expressions a = b (non-constant lvalue variation)
// and a = std::move(c) (const rvalue variation) had been declared.

// Note: Since expression #2 already requires the non-constant rvalue variation
// (a == std::move(b)) explicitly, expression #3 does not implicitly require it anymore.

// The type T meets the explicitly stated syntactic requirements of
// concept C above, but does not meet the additional implicit requirements
// (i.e., T satisfies but does not model C):
// a program requires C<T> is ill-formed (no diagnostic required).
struct T
{
    bool operator==(const T&) const { return true; }
    bool operator==(T&) = delete;
};

But it seems however, none of the latest compilers follow this rule(https://godbolt.org/z/84rK7rGTb - C<T> must evaluate to false). Am I missing something?


Solution

  • I quote from your quote:

    The extent to which an implementation validates the syntax of the variations is unspecified.

    ...

    // a program requires C<T> is ill-formed (no diagnostic required).
    

    A compiler is not required to validate, when checking C<T>, that, for T a, b, a == b is allowed.

    There is a difference between satisfying a concept and modeling a concept. Satisfaction of a concept C by T is defined by whether C<T> evaluates to true. Note that the rules for evaluating a requires expression say nothing about checking implicit expression variations. It would in fact be wrong for a compiler to evaluate C<T> to false in your example.

    Modeling a concept is a notion that can not in general be checked by a compiler. A concept is modeled when it is satisfied and the type behaves in the way it "should". For example, the following type

    struct U { bool operator==(U const&) const { return false; } };
    

    satisfies std::equality_comparable (since a == b, a != b etc. are well formed), but it does not model std::equality_comparable, since == is not a sensible notion of equality (it isn't reflexive). The implicit expression variations required by a concept are part of what it means to model the concept, but not what it means to satisfy the concept. They go above and beyond what a compiler is required to check.

    If a compiler does implement checks for whether types model a concept, that (again) must not manifest as the concept actually evaluating to false. Instead, it can give a warning or error when it notices that a concept evaluation returned true but the concept is not actually modeled. I'm not sure any compiler does this for any concept in practice.