Bug 98842 - optional's spaceship operations generates wrong code when operator== is not present
Summary: optional's spaceship operations generates wrong code when operator== is not p...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 11.0
: P3 normal
Target Milestone: 10.4
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2021-01-26 18:09 UTC by Nuno Lopes
Modified: 2024-03-28 11:24 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2021-01-26 00:00:00


Attachments
Patch operator<=> (311 bytes, patch)
2021-01-26 21:45 UTC, gcc
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Nuno Lopes 2021-01-26 18:09:59 UTC
struct expr {
  std::strong_ordering operator<=>(const expr &rhs) const;
  //bool operator==(const expr &rhs) const;
};

int f() {
  return std::is_eq(std::optional<expr>() <=> std::optional<expr>());
  //return std::optional<expr>() == std::optional<expr>();
}


Function f() is compiled to 0 like that. But if you uncomment the expr::operator== line, it returns 1.

https://gcc.godbolt.org/z/fv85eP
Comment 1 gcc 2021-01-26 18:56:27 UTC
Three problem lies in the concept three_way_comparable_with. It uses __detail::__weakly_eq_cmp_with that requires u == t. Due to paper P1185 operator== will not lookup operator<=>. Therefore having only operator<=> defined does not suffice to fulfill __detail::__weakly_eq_cmp. Hence the first of these two templates is SFINAE'D out and the second is taken. 

   template<typename _Tp, three_way_comparable_with<_Tp> _Up>
    constexpr compare_three_way_result_t<_Tp, _Up>
    operator<=>(const optional<_Tp>& __x, const optional<_Up>& __y)

  template<typename _Tp, typename _Up>
    constexpr compare_three_way_result_t<_Tp, _Up>
    operator<=>(const optional<_Tp>& __x, const _Up& __v) 

The second will however compare false whenever the left hand side is empty. 

The standard requires that three_way_comparable_with also requires weakly_equality_comparable. Hence the implementation of this concept is correct. However this might be a defect in the standard. 

Note that the standard also wants three_way_comparable_with<_Tp> for the second template (http://eel.is/c++draft/optional#comp.with.t-25). So the second template above is non-confirming. But it seems to me that a comparison between two optionals then is always ambiguous, so this should be a defect.
Comment 2 gcc 2021-01-26 21:45:24 UTC
Created attachment 50063 [details]
Patch operator<=>

The operator<=>(optional<_Tp>, _Up) is currently underconstrained. cf http://eel.is/c++draft/optional.comp.with.t#25 . This patch fixes this by adding the constraint three_way_comparable_with<_Tp> for _Up. Then in the program that started this bug report 

std::optional<expr>{} <=> std::optional<expr>{} 

will not compile if operator==(expr, expr) is not declared.
Comment 3 GCC Commits 2021-06-07 14:47:19 UTC
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:adec14811714e22a6c1f7f0199adc05370f0d8b0

commit r12-1260-gadec14811714e22a6c1f7f0199adc05370f0d8b0
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Mon Jun 7 13:02:15 2021 +0100

    libstdc++: Constrain three-way comparison for std::optional [PR 98842]
    
    The operator<=>(const optional<T>&, const U&) operator is supposed to be
    constrained with three_way_comparable_with<U, T> so that it can only be
    used when T and U are weakly-equality-comparable and also three-way
    comparable.
    
    Adding that constrain completely breaks std::optional comparisons,
    because it causes constraint recursion. To avoid that, an additional
    check that U is not a specialization of std::optional is needed. That
    appears to be a defect in the standard and should be reported to LWG.
    
    Signed-off-by: Jonathan Wakely <jwakely@redhat.com>
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/98842
            * include/std/optional (operator<=>(const optional<T>& const U&)):
            Add missing constraint and add workaround for template
            recursion.
            * testsuite/20_util/optional/relops/three_way.cc: Check that
            type without equality comparison cannot be compared when wrapped
            in std::optional.
Comment 4 GCC Commits 2021-06-11 22:25:58 UTC
The releases/gcc-11 branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:4f11586945fffd0ff808c16f2f341f9e85d83749

commit r11-8559-g4f11586945fffd0ff808c16f2f341f9e85d83749
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Mon Jun 7 13:02:15 2021 +0100

    libstdc++: Constrain three-way comparison for std::optional [PR 98842]
    
    The operator<=>(const optional<T>&, const U&) operator is supposed to be
    constrained with three_way_comparable_with<U, T> so that it can only be
    used when T and U are weakly-equality-comparable and also three-way
    comparable.
    
    Adding that constrain completely breaks std::optional comparisons,
    because it causes constraint recursion. To avoid that, an additional
    check that U is not a specialization of std::optional is needed. That
    appears to be a defect in the standard and should be reported to LWG.
    
    Signed-off-by: Jonathan Wakely <jwakely@redhat.com>
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/98842
            * include/std/optional (operator<=>(const optional<T>& const U&)):
            Add missing constraint and add workaround for template
            recursion.
            * testsuite/20_util/optional/relops/three_way.cc: Check that
            type without equality comparison cannot be compared when wrapped
            in std::optional.
    
    (cherry picked from commit adec14811714e22a6c1f7f0199adc05370f0d8b0)
Comment 5 GCC Commits 2021-07-22 15:34:31 UTC
The releases/gcc-10 branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:3a415b6a93765f29bffd0582a001bf03c4b93f3c

commit r10-9996-g3a415b6a93765f29bffd0582a001bf03c4b93f3c
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Mon Jun 7 13:02:15 2021 +0100

    libstdc++: Constrain three-way comparison for std::optional [PR 98842]
    
    The operator<=>(const optional<T>&, const U&) operator is supposed to be
    constrained with three_way_comparable_with<U, T> so that it can only be
    used when T and U are weakly-equality-comparable and also three-way
    comparable.
    
    Adding that constrain completely breaks std::optional comparisons,
    because it causes constraint recursion. To avoid that, an additional
    check that U is not a specialization of std::optional is needed. That
    appears to be a defect in the standard and should be reported to LWG.
    
    Signed-off-by: Jonathan Wakely <jwakely@redhat.com>
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/98842
            * include/std/optional (operator<=>(const optional<T>& const U&)):
            Add missing constraint and add workaround for template
            recursion.
            * testsuite/20_util/optional/relops/three_way.cc: Check that
            type without equality comparison cannot be compared when wrapped
            in std::optional.
    
    (cherry picked from commit adec14811714e22a6c1f7f0199adc05370f0d8b0)
Comment 6 Jonathan Wakely 2021-07-22 15:36:16 UTC
Fixed for 10.4 and 11.2
Comment 7 Jonathan Wakely 2024-03-28 11:24:31 UTC
(In reply to GCC Commits from comment #3)
>     Adding that constrain completely breaks std::optional comparisons,
>     because it causes constraint recursion. To avoid that, an additional
>     check that U is not a specialization of std::optional is needed. That
>     appears to be a defect in the standard and should be reported to LWG.

For the record, that is https://cplusplus.github.io/LWG/issue3566