Date
2023-06-21.00:00:00
Message id
13665

Content

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.