c++17 比较 string_view 与字符串时的歧义

c++17 Ambiguity when compare string_view with string

我看到 std::string_viewstd::string 都有对称的 operator==() 而对于 std::string 它有接受 std::string_view 的构造函数和将自身转换为的运算符std::string_view。所以当我们尝试使用 operator==() 比较 std::string_viewstd::string 时,它应该是模棱两可的吗?

我想我的想法肯定有问题。谁能澄清一下?

示例:

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) {...}

这样的比较不能有歧义的原因是 std::stringstd::string_view 都不是普通类型。相反,这些是 class 模板实例化,相应的比较运算符也是如此:

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;

这样定义的函数模板不考虑任何转换。相反,他们希望操作数的类型完全相同,因为只有这样推导才能成功(左右操作数的模板参数可以推导出相同的类型),从而产生一个可行的候选者。同样:

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

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

由于参数类型不匹配而失败,因为 T 不能是 intchar,尽管两者之间存在转换。还有:

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

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

失败,因为编译器无法从 my_string 推断出 basic_string<charT, traits, alloc>,尽管确实存在对其实例化的隐式转换。

然而,比较 s1 == s2 确实有效,因为标准库的实现有望提供重载,可以考虑从任何类型到 std::basic_string_view 的隐式转换(存在这样的隐式转换从 std::stringstd::string_view)。这可以通过例如禁止对其中一个参数进行推导来实现,如 [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;

通过将 __identity 中的一个操作数的类型定义为 template <class T> using __identity = decay_t<T>;,它引入了 non-deduced context,为某些 std::basic_string_view 和另一个参数创建了重载隐式转换为 std::basic_string_view class 模板的相同实例。

之所以有效,是因为 [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.

而Table62列出了所有的比较运算符,表达式两边都有视图。

由于 std::string 隐式转换为 std::string_view,因此将选择此重载。此类重载将与 s1 == s2 情况完全匹配,因此不会考虑隐式转换。

基本上,这是通过SFINAE工具实现的。像这样:

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);

这样的重载不需要隐式转换,因此比任何需要隐式转换的重载都要好。