在 MSVC 中,从 std::string_view 派生的对象比较不明确
Comparison for objects derived from std::string_view is ambiguous in MSVC
TL;DR:我是否可以期望下面的代码可以在任何符合c ++ 17的c ++工具链上编译(基于当前的c ++ 17提案(,并且MSVC的失败是其实现中的错误?
#include <string_view>
struct Foo : std::string_view {};
int main() {
Foo f1{};
Foo f2{};
return f1 == f2;
}
说明:
我有一个从std::string_view
派生的类,并且没有实现自己的比较运算符,因为std::string_view
语义正是我所需要的,我也希望它与例如std::string
相当。
但是,如果我尝试比较该类的两个实例,MSVC 2017 会抱怨具有类似转换的多个重载:
example.cpp
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/xlocale(314): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc
8 : <source>(8): error C2666: 'std::operator ==': 3 overloads have similar conversions
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/exception(336): note: could be 'bool std::operator ==(const std::exception_ptr &,const std::exception_ptr &) throw()' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/exception(341): note: or 'bool std::operator ==(std::nullptr_t,const std::exception_ptr &) throw()' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/exception(346): note: or 'bool std::operator ==(const std::exception_ptr &,std::nullptr_t) throw()' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/system_error(362): note: or 'bool std::operator ==(const std::error_code &,const std::error_code &) noexcept' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/system_error(370): note: or 'bool std::operator ==(const std::error_code &,const std::error_condition &) noexcept' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/system_error(378): note: or 'bool std::operator ==(const std::error_condition &,const std::error_code &) noexcept' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/system_error(386): note: or 'bool std::operator ==(const std::error_condition &,const std::error_condition &) noexcept' [found using argument-dependent lookup]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/xstring(970): note: or 'bool std::operator ==<char,std::char_traits<char>>(const std::basic_string_view<char,std::char_traits<char>>,const std::basic_string_view<char,std::char_traits<char>>) noexcept'
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/xstring(980): note: or 'bool std::operator ==<char,std::char_traits<char>,Foo&,void>(_Conv,const std::basic_string_view<char,std::char_traits<char>>) noexcept(<expr>)'
with
[
_Conv=Foo &
]
/opt/compiler-explorer/windows/19.10.25017/lib/native/include/xstring(990): note: or 'bool std::operator ==<char,std::char_traits<char>,Foo&,void>(const std::basic_string_view<char,std::char_traits<char>>,_Conv) noexcept(<expr>)'
with
[
_Conv=Foo &
]
8 : <source>(8): note: while trying to match the argument list '(Foo, Foo)'
Microsoft (R) C/C++ Optimizing Compiler Version 19.10.25017 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
Compiler exited with result code 2
我不知道,为什么列出前几个重载(例如带有std::error_code
(。由于错误消息本身只谈到 3 个重载,我想它们只是为了完整性,但不是问题的一部分。
然而,让我感到困惑的是这两个重载:
bool std::operator ==<char,std::char_traits<char>,Foo&,void>(_Conv,const std::basic_string_view<char,std::char_traits<char>>) noexcept(<expr>)
bool std::operator ==<char,std::char_traits<char>,Foo&,void>(const std::basic_string_view<char,std::char_traits<char>>,_Conv) noexcept(<expr>)
我在cppreference.com
上找不到任何提及它们的内容,并且代码在 clang 和 gcc: https://godbolt.org/g/4Lj5qv 下编译得很好,因此它们可能不会出现在它们的实现中。
所以我的问题是
- 它们的存在实际上是由预期的 c++17 标准允许(甚至强制要求(的,还是 MSVC 中的一个错误?
- 如果在符合标准的 c++ 标准库中允许这样的事情,是否有一个简单的解决方法不需要我自己实现所有比较器(我知道,编写它们很简单,但恕我直言,它应该不是必需的,我必须对多种类型重复该过程(。
编辑:
仅供参考,实际Foo
是一个不可变的字符串类,与此非常相似:https://codereview.stackexchange.com/questions/116010/yet-another-immutable-string,但为了简化设计,我想用std::string_view
替换我的手动卷str_ref
是的,你应该期望你的代码能够工作;模板参数推导可以在函数调用中推导出基类,请参阅 [temp.deduct.call]/4.3
— 如果
P
是一个类,并且P
具有simple-template-id的形式,则转换后的A
可以是推导的A
的派生类。
VS 2017 (15.3( 的问题是- 该标准还规定了其中一个参数可隐式转换为std::string_view
的情况,请参阅 [string.view.comparison]:
让
S
成为basic_string_view<charT, traits>
,sv
成为一个S
实例。实现应提供足够的附加 重载标记为constexpr
和noexcept
,以便对象t
根据表 67 可以比较到S
的隐式转换。表 67 — 其他
basic_string_view
比较重载
- 表达式
t == sv
等效于:S(t) == sv
- 表达式
sv == t
等效于:sv == S(t)
- 。
[ 示例:符合
operator==
的实现示例为:template<class T> using __identity = decay_t<T>; template<class charT, class traits> constexpr bool operator==(basic_string_view<charT, traits> lhs, basic_string_view<charT, traits> rhs) noexcept { return lhs.compare(rhs) == 0; } template<class charT, class traits> constexpr bool operator==(basic_string_view<charT, traits> lhs, __identity<basic_string_view<charT, traits>> rhs) noexcept { return lhs.compare(rhs) == 0; } template<class charT, class traits> constexpr bool operator==(__identity<basic_string_view<charT, traits>> lhs, basic_string_view<charT, traits> rhs) noexcept { return lhs.compare(rhs) == 0; }
— 结束示例 ]
这会导致VS 2017(15.3(出现问题,因为:
-
MSVC 编译器无法处理函数模板在非推导上下文中的部分排序(感谢 @T.C.(,因此标准中提到的实现是不可能的
-
因此,MSVC 标准库使用 SFINAE 处理重载 #2 和 #3,请参阅
xstring
:
template<class _Elem, class _Traits, class _Conv, // TRANSITION, VSO#265216 class = enable_if_t<is_convertible<_Conv, basic_string_view<_Elem, _Traits>>::value>> _CONSTEXPR14 bool operator==(_Conv&& _Lhs, const basic_string_view<_Elem, _Traits> _Rhs) _NOEXCEPT_OP(_NOEXCEPT_OP((basic_string_view<_Elem, _Traits>(_STD forward<_Conv>(_Lhs))))) { // compare objects convertible to basic_string_view instances for equality return (_Rhs._Equal(_STD forward<_Conv>(_Lhs))); } template<class _Elem, class _Traits, class _Conv, // TRANSITION, VSO#265216 class = enable_if_t<is_convertible<_Conv, basic_string_view<_Elem, _Traits>>::value>> _CONSTEXPR14 bool operator==(const basic_string_view<_Elem, _Traits> _Lhs, _Conv&& _Rhs) _NOEXCEPT_OP(_NOEXCEPT_OP((basic_string_view<_Elem, _Traits>(_STD forward<_Conv>(_Rhs))))) { // compare objects convertible to basic_string_view instances for equality return (_Lhs._Equal(_STD forward<_Conv>(_Rhs))); }
不幸的是,这与标准中的含义不同- 由于这些重载的签名与原始签名不同,并且Foo&&
比std::string_view
更好匹配(再次感谢 @T.C.(,#1、#2 和 #3 之间没有执行部分排序 - 重载分辨率选择 #2 和 #3 作为更好的候选者。现在这两者确实是模棱两可的——两者都是可行的,但都没有更专业。
作为一种解决方法,您可以为您的类型实现比较器,或者只是在两侧均可转换为string_view
时实现通用比较器:
#include <string_view>
template<class T, class T2,
class = std::enable_if_t<std::is_convertible<T, std::string_view>::value>,
class = std::enable_if_t<std::is_convertible<T2, std::string_view>::value>>
constexpr bool operator==(T&& lhs, T2&& rhs) noexcept
{
return lhs.compare(std::forward<T2>(rhs));
}
struct Foo : std::string_view {};
int main() {
Foo f1{};
Foo f2{};
return f1 == f2;
}
- 为什么使用 "this" 指针调用派生成员函数?
- 具有奇怪重复模板模式的派生类中的成员变量已损坏
- 在派生函数中指定void*参数
- 如何通过派生类函数更改基类中的向量
- 如何委托派生类使用其父构造函数?
- 如何使用单独文件中的派生类访问友元函数对象
- 派生类销毁的最佳实践是什么
- 如何使用基类指针引用派生类成员
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 如何引用基类的派生类?
- 存储模板类型以强制转换回派生<T>
- 需要从 istream 和 ostream 派生 iostream
- 在 C++ 中用派生类型重写成员函数
- 具有多个类、派生类的C++正向声明
- 有没有一种"cleaner"的方法可以在指向基的指针向量中找到派生类的第一个实例?
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 如果基类包含双指针成员,则派生类的构造函数
- 为什么此派生对象无法访问基类的后递减方法?
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践