Comparing objects derived from std :: string_view is ambiguous in MSVC

TL; DR: Can I expect the code below to be compiled to any C ++ 17 compatible C ++ toolchain (based on the current sentence in C ++ 17), and MSVC's refusal to do this is a mistake in their implementation?

#include <string_view> struct Foo : std::string_view {}; int main() { Foo f1{}; Foo f2{}; return f1 == f2; } 

Explanation:
I have a class that is derived from std::string_view and does not implement its own comparison operators, because the semantics of std::string_view is exactly what I need, and I also want it to be comparable with, for example, a std::string .

However, if I try to compare two instances of this 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 

I do not know why the first few overloads (for example, with std::error_code ) are listed at all. Since the error message itself only speaks of three overloads, I think they are only available for completeness, but are not part of the problem.

What bothers me is 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>) 

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

So my question is

  • Is their existence actually permitted (or even authorized) by the expected C ++ 17 standard, or is it an error in MSVC?
  • If something like this is allowed in the standard, appropriate C ++ standard library, is there a regular workaround that does not require me to implement all the comparators (I know that they are trivial to write, but this should not be necessary, and I would have to repeat the process for several types).

EDIT :
In fact, Foo is an immutable string class, very similar to this: https://codereview.stackexchange.com/questions/116010/yet-another-immutable-string , but in order to simplify the design, I wanted to replace my manual str_ref with std::string_view

+7
c ++ visual-c ++ c ++ - standard-library c ++ 17
source share
1 answer

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

- If P is a class and P has the form simple-template-id, the transformed A can be a derived class of the derived A

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

Let S be basic_string_view<charT, traits> , and sv be an instance of S Implementations should provide sufficient additional overloads, marked by constexpr and noexcept , so that object t with an implicit conversion to S can be compared in accordance with table 67.

Table 67 - Additional basic_string_view overload comparisons

  • The expression t == sv equivalent to: S(t) == sv
  • The expression sv == t equivalent to: sv == S(t)
  • .,.

[Example: an example implementation for operator== will be as follows:

 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 of example]

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

  • The MSVC compiler cannot handle partial ordering of wrt function templates (thanks @TC), so the implementation mentioned in the standard is not possible

  • Consequently, the MSVC standard library works around this 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))); } 

Unfortunately, this is not the same as the standard meant - since the signature of these overloads is different from the original, and Foo&& better than std::string_view (again thanks @TC), no partial orders are executed between # 1, # 2 and # 3 - overload resolution selects # 2 and # 3 as the best candidates. Now the two are truly ambiguous - both are viable, but not more specialized.

As a workaround, you can implement comparators for your types or just a general comparator when both sides can convert 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; } 
+6
source share

All Articles