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_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) ...
【问题讨论】:
你能提供一个模棱两可的代码示例吗? @LightnessRacesinOrbit:这有关系吗?他们都做同样的事情,所以这被称为本质上是诡辩。而且即使我们要进行诡辩,这也取决于参数的顺序,因此没有实际示例无法回答问题。 @NicolBolas 好吧,这不是诡辩,因为如果两个函数完全匹配某个调用,编译器会因模棱两可而出错。它不会先检查它们的语义等价性,然后让你侥幸逃脱。 @NicolBolas 当然它可以使用一些澄清和扩展,但我的意思是从根本上说,我不认为这是一个毫无意义或空洞的问题,正如你所暗示的那样? 【参考方案1】:这种比较不能模棱两可的原因是std::string
和std::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
不能是 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
到 @ 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 <class T> using __identity = decay_t<T>;
,它引入了non-deduced context,为一些std::basic_string_view
创建了重载,另一个参数隐式转换为@987654342 的相同实例化@类模板。
【讨论】:
谢谢,我知道这可能超出了这个问题的范围,但是decay_t<basic_string_view<charT, traits>>
如何强制进行隐式转换?
@goldenretriever std::decay_t<T>
是扩展为 typename std::decay<T>::type
的别名模板。编译器看到::type
是一个依赖类型(取决于T
)不会尝试推断T
。这是一个所谓的non-deduced context。但是,如果T
也用于另一个函数参数的定义,并且 可以 在那里推导,那么它将用于实例化非推导上下文中的参数。最终,你会得到 operator==
接受两个 basic_string_view
,但模板参数只来自一个 arg。
@goldenretriever 将参数传递给函数遵循只能使用隐式转换的复制初始化语义【参考方案2】:
这是因为[string.view.comparisons] 中有一个奇怪的子句:
设
S
为basic_string_view<charT, traits>
,sv
为S
的一个实例。实现应提供足够的额外重载,标记为constexpr
和noexcept
,以便可以根据表 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<class C, class T, class A> bool operator==(const basic_string<C, T, A>&, const basic_string<C, T, A>&)
,它仅在两个操作数的推导成功时才有效,因此它不考虑转换,这就是我的答案被删除的原因以上是关于c++17比较string_view和string时的歧义的主要内容,如果未能解决你的问题,请参考以下文章
如何在 constexpr string_view 上使用 std::string_view::remove_prefix()
比较 std::string_view 和子字符串 string_view