从std :: string_view派生的对象的比较在MSVC中是不明确的

时间:2017-08-18 08:23:59

标签: c++ visual-c++ c++17 c++-standard-library

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 ++标准库中允许这样的东西,是否有一个简单的解决方法并不要求我自己实现所有的比较器(我知道,它们写起来很简单,但它应该是imho没有必要,我必须为多种类型重复这个过程。

修改
仅供参考,实际Foo是一个非常类似于此的不可变字符串类:https://codereview.stackexchange.com/questions/116010/yet-another-immutable-string,但为了简化设计,我想用{替换我的手卷str_ref {1}}

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的实例。实施应提供足够的额外费用   标记为noexceptt的重载,以便对象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;
}