c++17 比较 string_view 与字符串时的歧义
c++17 Ambiguity when compare string_view with string
我看到 std::string_view
和 std::string
都有对称的 operator==()
而对于 std::string
它有接受 std::string_view
的构造函数和将自身转换为的运算符std::string_view
。所以当我们尝试使用 operator==()
比较 std::string_view
和 std::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::string
和 std::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
不能是 int
或 char
,尽管两者之间存在转换。还有:
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::string
到 std::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);
这样的重载不需要隐式转换,因此比任何需要隐式转换的重载都要好。
我看到 std::string_view
和 std::string
都有对称的 operator==()
而对于 std::string
它有接受 std::string_view
的构造函数和将自身转换为的运算符std::string_view
。所以当我们尝试使用 operator==()
比较 std::string_view
和 std::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::string
和 std::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
不能是 int
或 char
,尽管两者之间存在转换。还有:
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::string
到 std::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
bebasic_string_view<charT, traits>
, andsv
be an instance ofS
. Implementations shall provide sufficient additional overloads markedconstexpr
andnoexcept
so that an objectt
with an implicit conversion toS
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);
这样的重载不需要隐式转换,因此比任何需要隐式转换的重载都要好。