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下编译得很好,所以它们可能不会出现在它们的实现中。
所以我的问题是
修改:
仅供参考,实际Foo
是一个非常类似于此的不可变字符串类:https://codereview.stackexchange.com/questions/116010/yet-another-immutable-string,但为了简化设计,我想用{替换我的手卷str_ref
{1}}
答案 0 :(得分:6)
是的,你应该期望你的代码能够运作;模板参数推导可以在函数调用中推导出基类,参见[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
的重载,以便对象S
具有 可以根据表67比较隐式转换为basic_string_view
。表67 - 其他
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; }
的符合示例的示例将是:xstring
- 结束示例]
这导致VS 2017(15.3)出现问题,原因是:
MSVC编译器无法处理partial ordering of function templates w.r.t.非推断的上下文(感谢@ T.C。),因此标准中提到的实现不可能
因此,MSVC标准库适用于SFINAE,用于超载#2和#3,请参阅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
是比{{1}更好的匹配(再次感谢@TC),没有执行#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;
}