Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++17 Ambiguity when compare string_view with string

I saw both std::string_view and std::string have symmetric operator==() and for std::string it has the constructor to accept std::string_view and operator to convert itself to std::string_view. So when we try to use operator==() compare between std::string_view and std::string, is it supposed to be ambiguous?

I think there must be something wrong with my thoughts. Can anyone clarify?

Example:

std::string s1 = "123";
std::string_view s2 = "123";
// in the following comparison, will s1 use the convert operator to generate a string_view, or will s2 use string's string_view constructor to generate a string?
if (s1 == s2) {...}
like image 289
golden retriever Avatar asked Sep 12 '18 00:09

golden retriever


People also ask

Why can't a comparison between string and string_view be ambiguous?

The reason such a comparison cannot be ambiguous is that neither std::string nor std::string_view are plain types. Instead, these are class templates instantiations, and so are respective comparison operators: Such defined function templates do not consider any conversions.

What is string_view in C++17?

std::string_view: C++17 library has proposed a standard type of string ( std::string_view) which is different from the usual std::string.

How to modify string_view in c++20?

Modification of std::string_view: In C++20 some new functions have been added like str.remove_suffix (), str.remove_suffix () using these functions can modify the std::string_view. Like can remove the selective suffix or prefixes of the given string.

How to illustrate the char type in C++?

Below is the C++ program to illustrate the Char Type: Example Of std::string_view: Below examples, show that most of the string-based functions can be used with std::string_view as used str.compare (), str.back (), str.cend (), and str.at () functions.


Video Answer


2 Answers

The reason such a comparison cannot be ambiguous is that neither std::string nor std::string_view are plain types. Instead, these are class templates instantiations, and so are respective comparison operators:

template <class charT, class traits, class alloc>
constexpr bool operator==(const basic_string<charT, traits, alloc>& lhs,
                          const basic_string<charT, traits, alloc>& rhs) noexcept;

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

Such defined function templates do not consider any conversions. Instead, they expect the operands to be of exactly the same type, as only then the deduction succeeds (the same types can be deduced for template parameters of left and right operands), producing a viable candidate. Similarly:

template <typename T>
void foo(T, T);

foo(42, 'x'); // error

fails due to mismatch of types of arguments, as T cannot be either int or char, although conversions between the two exist. Also:

struct my_string
{
    operator std::string() const { return ""; }
};

std::string s;
my_string ms;
s == ms; // error

fails, because the compiler cannot deduce basic_string<charT, traits, alloc> from my_string, although there does exists an implicit conversion to its instantiation.

The comparison s1 == s2 does, however, work, because the implementation of the standard library is expected to provide overloads that can consider implicit conversions from any type to std::basic_string_view (such an implicit conversion exists from std::string to std::string_view). This can be achieved, e.g., by inhibiting deduction for one of the parameters, as shown in the example part of [string.view.comparison]/p1:

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

By putting the type of one of the operands in __identity defined as template <class T> using __identity = decay_t<T>;, it introduces a non-deduced context, creating an overload for some std::basic_string_view and another argument implicitly convertible to the same instantiation of the std::basic_string_view class template.

like image 71
Piotr Skotnicki Avatar answered Sep 21 '22 05:09

Piotr Skotnicki


This works because of an odd clause in [string.view.comparisons]:

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 62.

And Table 62 lists all of the comparison operators, with the view on either side of the expression.

Since std::string has an implicit conversion to std::string_view, it is this overload which will be chosen. Such overloads will have an exact match to the s1 == s2 case, so implicit conversions will not be considered.

Basically, this is implemented through SFINAE tools. Something like this:

template<typename Str>
std::enable_if_t<std::is_convertible_v<std::string_view, Str>, bool> operator==(const Str &rhs, const std::string_view &lhs);

Such an overload doesn't require implicit conversions, so it's better than any overload that does.

like image 39
Nicol Bolas Avatar answered Sep 25 '22 05:09

Nicol Bolas