c++17比较string_view和string时的歧义

Posted

技术标签:

【中文标题】c++17比较string_view和string时的歧义【英文标题】:c++17 Ambiguity when compare string_view with string 【发布时间】:2018-09-12 00:50:22 【问题描述】:

我看到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) ...

【问题讨论】:

你能提供一个模棱两可的代码示例吗? @LightnessRacesinOrbit:这有关系吗?他们都做同样的事情,所以这被称为本质上是诡辩。而且即使我们要进行诡辩,这也取决于参数的顺序,因此没有实际示例无法回答问题。 @NicolBolas 好吧,这不是诡辩,因为如果两个函数完全匹配某个调用,编译器会因模棱两可而出错。它不会先检查它们的语义等价性,然后让你侥幸逃脱。 @NicolBolas 当然它可以使用一些澄清和扩展,但我的意思是从根本上说,我不认为这是一个毫无意义或空洞的问题,正如你所暗示的那样? 【参考方案1】:

这种比较不能模棱两可的原因是std::stringstd::string_view 都不是普通类型。相反,这些是类模板实例化,相应的比较运算符也是如此:

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&lt;charT, traits, alloc&gt;,尽管确实存在对其实例化的隐式转换。

然而,比较 s1 == s2 确实有效,因为标准库的实现预计会提供重载,可以考虑从任何类型到 std::basic_string_view 的隐式转换(这种隐式转换存在从 std::string 到 @ 987654337@)。这可以通过例如禁止对参数之一进行扣除来实现,如[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 &lt;class T&gt; using __identity = decay_t&lt;T&gt;;,它引入了non-deduced context,为一些std::basic_string_view 创建了重载,另一个参数隐式转换为@987654342 的相同实例化@类模板。

【讨论】:

谢谢,我知道这可能超出了这个问题的范围,但是decay_t&lt;basic_string_view&lt;charT, traits&gt;&gt; 如何强制进行隐式转换? @goldenretriever std::decay_t&lt;T&gt; 是扩展为 typename std::decay&lt;T&gt;::type 的别名模板。编译器看到::type 是一个依赖类型(取决于T)不会尝试推断T。这是一个所谓的non-deduced context。但是,如果T 也用于另一个函数参数的定义,并且 可以 在那里推导,那么它将用于实例化非推导上下文中的参数。最终,你会得到 operator== 接受两个 basic_string_view,但模板参数只来自一个 arg。 @goldenretriever 将参数传递给函数遵循只能使用隐式转换的复制初始化语义【参考方案2】:

这是因为[string.view.comparisons] 中有一个奇怪的子句:

Sbasic_­string_­view&lt;charT, traits&gt;svS 的一个实例。实现应提供足够的额外重载,标记为 constexprnoexcept,以便可以根据表 62 比较具有隐式转换为 S 的对象 t

表 62 列出了所有比较运算符,视图位于表达式的两侧。

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

这样的重载不需要隐式转换,所以它比任何重载都好。

【讨论】:

嗯...该段并没有说这样的重载应该生成完全匹配,并且下面的“示例符合实现”不是基于函数模板会这样做的。 @PiotrSkotnicki:“应提供足够的额外重载......以便可以比较隐式转换为 S 的对象 t”。如果它们不完全匹配,则比较与string_view 比较会产生歧义,因此无法进行比较。因此,完全匹配的要求是一项要求。此外,几乎没有办法实现这样的事情没有也使其完全匹配,甚至忽略歧义的产生。 使用 string_view 比较运算符的比较不能模棱两可,因为每个运算符都希望为两个操作数推导出完全相同的模板参数。符合示例的实现将操作数之一置于非推导上下文中,强制在调用站点隐式转换为 string_view @PiotrSkotnicki:我不明白你的意思。示例代码解释了如何编写一个仅适用于可隐式转换为string_view 的类型的函数。它并不意味着是对operator== 标准要求的完整和完整的实现。 @goldenretriever 没有bool operator==(string, string)。取而代之的是template&lt;class C, class T, class A&gt; bool operator==(const basic_string&lt;C, T, A&gt;&amp;, const basic_string&lt;C, T, A&gt;&amp;),它仅在两个操作数的推导成功时才有效,因此它不考虑转换,这就是我的答案被删除的原因

以上是关于c++17比较string_view和string时的歧义的主要内容,如果未能解决你的问题,请参考以下文章

如何在 constexpr string_view 上使用 std::string_view::remove_prefix()

比较 std::string_view 和子字符串 string_view

std::string_view 编译时散列

为啥 std::string_view 不是微不足道的?

我啥时候应该使用 std::string / std::string_view 作为参数/返回类型

哈希<std::string> 与哈希<std::string_view>