从std :: string_view派生的对象的比较在MSVC中不明确 [英] Comparison for objects derived from std::string_view is ambiguous in MSVC

查看:137
本文介绍了从std :: string_view派生的对象的比较在MSVC中不明确的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TL; DR:
我可以期望下面的代码将在任何符合c ++ 17要求的c ++工具链(基于当前c ++ 17提议)上编译并且失败

TL;DR: Can I expect that the code below will compile on any c++17 conformant c++ toolchain (based on the current c++17 proposal) and the failure of MSVC to do so is a bug in their implementation?

#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

Explanation:
I have a class that is derived from std::string_view and doesn't implement its own comparison operators, because the std::string_view semantics are exactly what I need and I also want it to be comparable to e.g. a std::string.

但是,如果我尝试比较该类的两个实例,MSVC 2017会抱怨具有类似转换的多个重载:

However, if I try to compare two instances of that class, MSVC 2017 complains about multiple overloads with similar conversions:

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个重载,我想它们只是出于完整性考虑,而不是问题的一部分。

I do not know, why the the first few overloads (e.g. with std::error_code) are listed at all. As the error message itself only talks about 3 overloads I guess they are only there for completeness, but are not part of the problem.

这两个重载让我感到困惑:

What confuses me however are those two overloads:

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 ,因此它们可能不在实现中。

I could not find any mention of them on cppreference.com and the code compiles fine under clang and gcc: https://godbolt.org/g/4Lj5qv, so they are probably not present in their implementation.

我的问题是


  • 预期的c ++ 17标准是否确实允许(甚至强制要求)它们的存在?或者这是MSVC中的错误吗?

  • 如果在符合标准的c ++标准库中允许这样的操作,是否有一个简单的解决方法,不需要我自己实现所有比较器(我知道,它们对于写,但是应该没必要,我必须对多种类型重复该过程。)

编辑

仅供参考,实际的 Foo 是一个不可变的字符串类,与该类非常相似:https://codereview.stackexchange.com/questions/116010/yet-another-immutable-string ,但顺序为了简化设计,我想用 std :: string_view

EDIT:
Just for reference, the actual Foo is an immutable string class very similar to this one: https://codereview.stackexchange.com/questions/116010/yet-another-immutable-string, but in order to simplify the design I wanted to replace my hand-rolled str_ref with std::string_view

推荐答案

是的,您应该期望代码能够正常工作;模板参数推导可以推导函数调用中的基类,请参见 [temp.deduct .call] /4.3

Yes you should expect your code to work; template argument deduction can deduce the base class in function calls, see [temp.deduct.call]/4.3


—如果 P 是一个类,而 P 的格式为 simple-template-id ,则转换后的 A 可以是推导的 A 的派生类。

— 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)的问题是-该标准还规定了其中一个参数是隐式可转换的情况 em>到 std :: string_view ,请参见 [string.view.comparison]

The issue with VS 2017 (15.3) is - the standard also has provisions for situations when one of the arguments is implicitly-convertible to std::string_view, see [string.view.comparison]:


S basic_string_view< charT,traits> ,而 sv 是<$ c $的
实例c> S 。实现应提供足够的附加
重载,分别标记为 constexpr noexcept ,以使对象 t
隐式转换为 S 可以根据表67进行比较。

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.

表67 —其他 basic_string_view 比较重载

Table 67 — Additional basic_­string_­view comparison overloads


  • 表达式 t == sv 等效于: S(t)== sv

  • 表达式 sv == t 等效于: sv == S(t)

  • 。 。 。

  • Expression t == sv equivalent to: S(t) == sv
  • Expression sv == t equivalent to: sv == S(t)
  • . . .

[示例: operator == 的示例符合实现是:

[ 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;
  }

-示例]

这会在VS 2017(15.3)中引起问题,因为:

This causes a problem in VS 2017 (15.3) because:

  • The MSVC compiler can't handle partial ordering of function templates w.r.t. non-deduced context (thanks @T.C.), so the implementation mentioned in the standard is not possible

因此,MSVC标准库可以使用SFINAE解决重载#2和#3的问题,请参见 xstring

Consequently, the MSVC standard library works around that with SFINAE for overloads #2 and #3, see 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 (再次感谢@TC),没有执行#1,#2和#3之间的部分排序-重载分辨率选择#2和#3作为更好的候选者。现在,这两个确实是模棱两可的-都可行,但都不是更专业。

Unfortunately this is not the same as what was meant in the standard - since the signature of these overloads differs from the original one, and Foo&& is a better match than std::string_view (again thanks @T.C.), no partial ordering between #1, #2 and #3 is performed - overload resolution selects #2 and #3 as better candidates. Now these two are truly ambiguous - both are viable yet neither is more specialized.

作为一种变通办法,您可以为您的类型实现比较器,或者仅对两个类型都使用通用比较器双方都可以转换为 string_view

As a workaround you can implement comparators for your types, or just a generic comparator for when both sides are convertible to 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;
}

这篇关于从std :: string_view派生的对象的比较在MSVC中不明确的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆