Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[class.spaceship] lacks to specify the behavior if the defined expression for synthesized three-way comparison is ill-formed #5338

Open
xmh0511 opened this issue Mar 9, 2022 · 10 comments

Comments

@xmh0511
Copy link
Contributor

xmh0511 commented Mar 9, 2022

Consider this example:

struct C{
   int operator <=>(C const&) const noexcept = default;
   int a = 0;
};
int main(){
    using type = decltype(&C::operator<=>);
}

GCC:

<source>:8:39: error: use of deleted function 'constexpr int C::operator<=>(const C&) const'
    8 |     using type = decltype(&C::operator<=>);
      |                                       ^~~
<source>:4:8: note: 'constexpr int C::operator<=>(const C&) const' is implicitly deleted because the default definition would be ill-formed:
    4 |    int operator <=>(C const&) const noexcept = default;
      |        ^~~~~~~~
<source>:5:8: error: three-way comparison of 'C::a' has type 'std::strong_ordering', which does not convert to 'int'
    5 |    int a = 0;

GCC and Clang have a divergence implementation on the point that if the defined expression is ill-formed. Specifically, the expanded list of subobjects for an object x of type C consists of {C::a}, where the synthesized three-way comparison is defined as static_cast<int>(a<=>a) as per [class.spaceship] p1.1. Since the operand of the operator <=> in the defined expression has type int, built-in expression interpretation applies to the operator, its type is std​::​strong_­ordering whose prvalue cannot be converted to type int. Thus, the defined expression static_cast<int>(a<=>a) is ill-formed, on this point, GCC thinks the defaulted three-way comparison function should be deleted while Clang thinks it's ok(not be deleted). However, [class.spaceship.note] p1 wants to imply something regarding this case. However, it's not a formal rule and no overload resolution performs in this case too.

@frederick-vs-ja
Copy link
Contributor

Isn't this the case for C<Weak> in P1186R3?

@xmh0511
Copy link
Contributor Author

xmh0511 commented Mar 10, 2022

Isn't this the case for C<Weak> in P1186R3?

Thanks, that paper has listed a large of particular cases that can expose the problem to which this issue is concerning. From [class.spaceship.note] p1, the standard prefers to consider these synthesized three-way comparisons to be ill-formed, thus I agree with the opinion thereof in that paper: Erring on the side of ill-formed helps programmers catch bugs earlier; rather than just delete the defaulted function instead.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Mar 10, 2022

The proposal in that paper says:

If the declared return type of a defaulted three-way comparison operator function is R and the synthesized three-way comparison for comparison category type R between any objects xi and xi is not defined or would be ill-formed, the operator function is defined as deleted.

Conversely, the current draft only talks about "is not defined". For the example given in this issue, it has a defined synthesized three-way comparison, merely the defined expression is ill-formed. The case falls into the "would be ill-formed", however, the current draft lacks to specify such a condition in the formal rules.

Improvement:

Let R be the declared return type of a defaulted three-way comparison operator function, and let
xi be the elements of the expanded list of subobjects for an object x of type C.

  • [...]
  • Otherwise, R shall not contain a placeholder type. If the synthesized three-way comparison of type R between any objects xi and xi is not defined or would be ill-formed, the operator function is defined as deleted.

@jensmaurer
Copy link
Member

Not an improvement. "If ill-formed, then deleted" is too far-reaching to be implementable.

See CWG2432.

@frederick-vs-ja
Copy link
Contributor

frederick-vs-ja commented Mar 10, 2022

Using "would be ill-formed" here is not an improvement but a regression.

The "would be ill-formed" part was removed by P2002R1, following the rationale described in P1630R1: no function should be instantiated when determining whether a defaulted function is deleted.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Mar 10, 2022

@jensmaurer @frederick-vs-ja

The other opinion is, whether the defaulted function is defined as deleted, which is only determined by whether the synthesized three-way comparison is defined; This keeps the same as the current draft did. However, if once the defaulted function is odr-used or its definition is required, the prior synthesized three-way comparison is rechecked to determine whether the program is well-formed as if the synthesized three-way comparison would appear in the definition of the defaulted function.

This is consistent with what Clang has done.

@jensmaurer
Copy link
Member

And that's consistent with what we do for defaulted constructors: In a first phase, we check whether the default constructor is deleted; in a second phase (when the definition is needed), we compile the actual definition (which might end up being ill-formed).

And this seems to be the intent here, too, and seems to be supported by the existing wording, so maybe it's enough to add a note.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Mar 10, 2022

@jensmaurer For default constructor or other special member functions, we explicitly define the condition that the defaulted default constructor should be defined as deleted. For ill-formed cases, I think they are regulated by [class.base.init] together with [dcl.init]. Such as the default initialization of the member of reference type. we explicitly state such a case will be ill-formed through [dcl.init.general] p10.

The difference here is that we didn't explicitly phrase what will happen when evaluating the defaulted three-way comparison except that we may intend to imply this point by specifying the return value of the comparison in [class.spaceship] p3. Maybe, we could explicitly state the "ill-formed" story in [class.spaceship] p3:

until the first index i where the synthesized three-way comparison of type R between xi and yi yields a result value vi where vi != 0, contextually converted to bool, yields true; V is a copy of vi.

It seems that the rule intends to say the evaluation of the default comparison will evaluate the synthesized comparison of the subobjects. However, it's not explicit. Maybe, we could say: if any synthesized three-way comparison of type R between xi and yi results in an invalid expression, the program is ill-formed. I think this sentence is close to what the implementation did.

@jensmaurer
Copy link
Member

The point is still that the constructor descriptions rely on specifications elsewhere to get to the ill-formed cases. I'm not seeing why we need to be normatively different for the comparison operators. I do agree that the "deleted" and "use this expression" cases are a bit more interwoven for the comparison functions.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Mar 11, 2022

@jensmaurer I think we just need to specify what expressions are in the implicit definition of a defaulted comparison operator function, all the issues mentioned above will be resolved.

Consider [except.spec] p11

The exception specification for a comparison operator function ([over.binary]) without a noexcept-specifier that is defaulted on its first declaration is potentially-throwing if and only if any expression in the implicit definition is potentially-throwing.

we seem to haven't yet defined the concept for what expressions are in the implicit definition. For a defaulted three-way comparison, the expressions presumably are these synthesized three-way comparisons for the subobjects in the object x of class C of which the defaulted function is a member, which are used to determine the return value. The same is true of defaulted equality operator function. For these defaulted functions, we just specify their return values, we never explicitly phrase that these expressions that are used to determine the return value appear within the implicit definitions.

So, I think we lack to define the concept for what expressions are in the implicit definition of a defaulted function. This can clarify [except.spec] p11, and it can naturally interpret why the invocation of the defaulted function or the context where the definition is required will cause the program ill-formed if any expression in the implicit definition is invalid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants