std::basic_string_view comparison operators are overspecifiedSection: 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).
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<=>).
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
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 regardingoperator<=> and operator== were merged:
operator==(bsv, bsv) to deal with sv == sv;
operator==(bsv, type_identity_t<bsv>) and
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.
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.
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[…]
Modify 27.3.4 [string.view.comparison] as indicated:
-1- LetSbebasic_string_view<charT, traits>, andsvbe an instance ofS. Implementations shall provide sufficient additional overloads markedconstexprandnoexceptso that an objecttwith an implicit conversion toScan be compared according to Table 81 [tab:string.view.comparison.overloads].
Table 81: Additionalbasic_string_viewcomparison overloads [tab:string.view.comparison.overloads]ExpressionEquivalent tot == svS(t) == svsv == tsv == S(t)t != svS(t) != svsv != tsv != S(t)t < svS(t) < svsv < tsv < S(t)t > svS(t) > svsv > tsv > S(t)t <= svS(t) <= svsv <= tsv <= S(t)t >= svS(t) >= svsv >= tsv >= S(t)t <=> svS(t) <=> svsv <=> tsv <=> S(t)
[Example 1: A sample conforming implementation foroperator==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
-4- Mandates:Rdenote the typetraits::comparison_categoryif that qualified-id is valid and denotes a type (13.10.3 [temp.deduct]), otherwiseRisweak_ordering.Rdenotes a comparison category type (17.12.2 [cmp.categories]). -5- Returns:static_cast<R>(lhs.compare(rhs) <=> 0). [Note: The usage oftype_identity_tas parameter ensures that an object of typebasic_string_view<charT, traits>can always be compared with an object of a typeTwith an implicit conversion tobasic_string_view<charT, traits>, and vice versa, as per 12.2.2.3 [over.match.oper]. — end note]