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;
}
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
.
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
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>)
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.
So my question is
- Is their existence actually allowed (or even mandated) by the expected c++17 standard or is that a bug in MSVC?
- If something like this is allowed in a standard conforming c++ standard library, is there a simple workaround that doesn't require me to implement all the comparators myself (I know, they are trivial to write, but it should imho not be necessary and I'd have to repeat the process for multiple types).
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
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
— If
P
is a class andP
has the form simple-template-id, then the transformedA
can be a derived class of the deducedA
.
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]:
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 ]
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
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))); }
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.
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;
}
来源:https://stackoverflow.com/questions/45751978/comparison-for-objects-derived-from-stdstring-view-is-ambiguous-in-msvc