This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.

3950. std::basic_string_view comparison operators are overspecified

Section: 27.3.2 [string.view.synop] Status: WP Submitter: Giuseppe D'Angelo Opened: 2023-06-21 Last modified: 2024-04-02

Priority: Not Prioritized

View all issues with WP status.

Discussion:

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

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

In 27.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 27.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.

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

Proposed resolution:

This wording is relative to N4950.

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

    […]
    // 27.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 27.3.4 [string.view.comparison], sufficient additional overloads of comparison functions
    […]
    
  2. Modify 27.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]