3950. std::basic_string_view comparison operators are overspecified

Section: 23.3.2 [string.view.synop] Status: Ready Submitter: Giuseppe D'Angelo Opened: 2023-06-21 Last modified: 2023-11-10 22:10:49 UTC

Priority: Not Prioritized

View all issues with Ready status.

Discussion:

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

// 23.3.4 [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 23.3.4 [string.view.comparison], sufficient additional overloads of comparison functions

In 23.3.4 [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 23.3.4 [string.view.comparison], but it is also sufficient, as all the following cases

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.

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

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

Proposed resolution:

This wording is relative to N4950.

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

    […]
    // 23.3.4 [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 23.3.4 [string.view.comparison], sufficient additional overloads of comparison functions
    […]
    
  2. Modify 23.3.4 [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 (13.10.3 [temp.deduct]), otherwise R is weak_ordering.

    -4- Mandates: R denotes a comparison category type (17.11.2 [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 12.2.2.3 [over.match.oper]. — end note]