从 std::string_view 派生的对象的比较在 MSVC 中不明确
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
派生的 class 并且没有实现自己的比较运算符,因为 std::string_view
语义正是我所需要的,我也希望它与例如一个std::string
。
但是,如果我尝试比较 class 的两个实例,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
是一个不可变的字符串 class 与这个非常相似: https://codereview.stackexchange.com/questions/116010/yet-another-immutable-string,但为了简化设计我想更换我的手卷 str_ref
与 std::string_view
是的,您应该期望您的代码能够工作;模板参数推导可以推导函数调用中的基础class,参见[temp.deduct.call]/4.3
— If P
is a class and P
has the form simple-template-id, then the transformed A
can be a derived class of the deduced A
.
VS 2017 (15.3) 的问题是 - 该标准还针对其中一个参数隐式可转换 到 std::string_view
,参见 [string.view.comparison]:
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 67.
Table 67 — Additional basic_string_view
comparison overloads
- Expression
t == sv
equivalent to: S(t) == sv
- Expression
sv == t
equivalent to: sv == S(t)
- . . .
[ Example: A sample conforming implementation for operator==
would be:
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;
}
— end example ]
这会导致 VS 2017 (15.3) 出现问题,因为:
MSVC 编译器无法处理 partial ordering of function templates w.r.t。非推导上下文(感谢@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;
}
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
派生的 class 并且没有实现自己的比较运算符,因为 std::string_view
语义正是我所需要的,我也希望它与例如一个std::string
。
但是,如果我尝试比较 class 的两个实例,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
是一个不可变的字符串 class 与这个非常相似: https://codereview.stackexchange.com/questions/116010/yet-another-immutable-string,但为了简化设计我想更换我的手卷 str_ref
与 std::string_view
是的,您应该期望您的代码能够工作;模板参数推导可以推导函数调用中的基础class,参见[temp.deduct.call]/4.3
— If
P
is a class andP
has the form simple-template-id, then the transformedA
can be a derived class of the deducedA
.
VS 2017 (15.3) 的问题是 - 该标准还针对其中一个参数隐式可转换 到 std::string_view
,参见 [string.view.comparison]:
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 67.Table 67 — Additional
basic_string_view
comparison overloads
- Expression
t == sv
equivalent to:S(t) == sv
- Expression
sv == t
equivalent to:sv == S(t)
- . . .
[ Example: A sample conforming implementation for
operator==
would be: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; }
— end example ]
这会导致 VS 2017 (15.3) 出现问题,因为:
MSVC 编译器无法处理 partial ordering of function templates w.r.t。非推导上下文(感谢@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;
}