Title
std::basic_string_view comparison operators are overspecified
Status
wp
Section
[string.view.synop]
Submitter
Giuseppe D'Angelo

Created on 2023-06-21.00:00:00 last changed 7 months ago

Messages

Date: 2024-04-02.10:29:12

Proposed resolution:

This wording is relative to N4950.

  1. Modify [string.view.synop], header <string_view> synopsis, as indicated:

    […]
    // [string.view.comparison], non-member comparison functions
    template<class charT, class traits>
      constexpr bool operator==(basic_string_view<charT, traits> x,
                                type_identity_t<basic_string_view<charT, traits>> y) noexcept;
    
    template<class charT, class traits>
      constexpr see below operator<=>(basic_string_view<charT, traits> x,
                                      type_identity_t<basic_string_view<charT, traits>> y) noexcept;
    
    // see [string.view.comparison], sufficient additional overloads of comparison functions
    […]
    
  2. Modify [string.view.comparison] as indicated:

    -1- Let S be basic_string_view<charT, traits>, and sv be an instance of S. Implementations shall provide sufficient additional overloads marked constexpr and noexcept so that an object t with an implicit conversion to S can be compared according to Table 81 [tab:string.view.comparison.overloads].

    Table 81: Additional basic_string_view comparison overloads [tab:string.view.comparison.overloads]
    Expression Equivalent to
    t == sv S(t) == sv
    sv == t sv == S(t)
    t != sv S(t) != sv
    sv != t sv != S(t)
    t < sv S(t) < sv
    sv < t sv < S(t)
    t > sv S(t) > sv
    sv > t sv > S(t)
    t <= sv S(t) <= sv
    sv <= t sv <= S(t)
    t >= sv S(t) >= sv
    sv >= t sv >= S(t)
    t <=> sv S(t) <=> sv
    sv <=> t sv <=> S(t)

    [Example 1: A sample conforming implementation for operator== would be:

    template<class charT, class traits>
      constexpr bool operator==(basic_string_view<charT, traits> lhs,
                                basic_string_view<charT, traits> rhs) noexcept {
        return lhs.compare(rhs) == 0;
      }
    template<class charT, class traits>
      constexpr bool operator==(basic_string_view<charT, traits> lhs,
                                type_identity_t<basic_string_view<charT, traits>> rhs) noexcept {
        return lhs.compare(rhs) == 0;
      }
    

    end example]

    template<class charT, class traits>
      constexpr bool operator==(basic_string_view<charT, traits> lhs,
                                type_identity_t<basic_string_view<charT, traits>> rhs) noexcept;
    
    

    -2- Returns: lhs.compare(rhs) == 0.

    template<class charT, class traits>
      constexpr see below operator<=>(basic_string_view<charT, traits> lhs,
                                      type_identity_t<basic_string_view<charT, traits>> rhs) noexcept;
    

    -3- Let R denote the type traits::comparison_category if that qualified-id is valid and denotes a type ([temp.deduct]), otherwise R is weak_ordering.

    -4- Mandates: R denotes a comparison category type ([cmp.categories]).

    -5- Returns: static_cast<R>(lhs.compare(rhs) <=> 0).

    [Note: The usage of type_identity_t as parameter ensures that an object of type basic_string_view<charT, traits> can always be compared with an object of a type T with an implicit conversion to basic_string_view<charT, traits>, and vice versa, as per [over.match.oper]. — end note]

Date: 2024-04-02.10:29:12

[ Tokyo 2024-03-23; Status changed: Voting → WP. ]

Date: 2023-11-10.22:10:49

[ Kona 2023-11-10; move to Ready ]

Editorial issue 6324 provides the changes as a pull request to the draft.

Date: 2023-06-21.00:00:00

The <string_view> synopsis in [string.view.synop] has these signatures for operator== and operator<=>:

// [string.view.comparison], non-member comparison functions
template<class charT, class traits>
  constexpr bool operator==(basic_string_view<charT, traits> x,
                            basic_string_view<charT, traits> y) noexcept;
template<class charT, class traits>
  constexpr see below operator<=>(basic_string_view<charT, traits> x,
                                  basic_string_view<charT, traits> y) noexcept;

// see [string.view.comparison], sufficient additional overloads of comparison functions

In [string.view.comparison], paragraph 1 states that "Implementations shall provide sufficient additional overloads" so that all comparisons between a basic_string_view<C, T> object and an object of a type convertible to basic_string_view<C, T> work (with the reasonable semantics).

The associated Example 1 proposes this implementation strategy for operator==:

template<class charT, class traits>
  constexpr bool operator==(basic_string_view<charT, traits> lhs,
                            basic_string_view<charT, traits> rhs) noexcept {
    return lhs.compare(rhs) == 0;
  }
template<class charT, class traits>
  constexpr bool operator==(basic_string_view<charT, traits> lhs,
                            type_identity_t<basic_string_view<charT, traits>> rhs) noexcept {
    return lhs.compare(rhs) == 0;
  }

With the current semantics of rewritten candidates for the comparison operators, it is however superfluous to actually specify both overloads (the same applies for operator<=>).

The second overload (using type_identity_t) is indeed necessary to implement the "sufficient additional overloads" part of [string.view.comparison], but it is also sufficient, as all the following cases

  • sv == sv

  • sv == convertible_to_sv

  • convertible_to_sv == sv

can in fact use it (directly, or after being rewritten e.g. with the arguments swapped).

The reason why we still do have both operators seems to be historical; there is an explanation offered here by Barry Revzin.

Basically, there were three overloads before a bunch of papers regarding operator<=> and operator== were merged:

  1. operator==(bsv, bsv) to deal with sv == sv;

  2. operator==(bsv, type_identity_t<bsv>) and

  3. operator==(type_identity_t<bsv>, bsv) to deal with sv == convertible_to_sv and vice versa.

Overload (1) was necessary because with only (2) and (3) a call like sv == sv would otherwise be ambiguous. With the adoption of the rewriting rules, overload (3) has been dropped, without realizing that overload (1) would then become redundant.

The specification of these overloads can be greatly simplified by adjusting the signatures to explicitly use type_identity_t.

History
Date User Action Args
2024-04-02 10:29:12adminsetmessages: + msg14032
2024-04-02 10:29:12adminsetstatus: voting -> wp
2024-03-18 09:32:04adminsetstatus: ready -> voting
2023-11-10 19:15:47adminsetmessages: + msg13831
2023-11-10 19:15:47adminsetstatus: new -> ready
2023-06-25 10:47:28adminsetmessages: + msg13666
2023-06-21 00:00:00admincreate