I'm trying to understand how operator==
gets considered for overload resolution in the following example in the standard library implementation for Clang or MSVC.
#include <string>
#include <string_view>
//#define WORKAROUND 1
namespace tst
{
template<class T>
class string_view
{
#if WORKAROUND
friend bool operator==( string_view a, string_view b);
#endif
};
template<class T, class A>
class string
{
public:
operator string_view<T>() const;
};
#if !WORKAROUND
template<class T>
bool operator==( string_view<T> a, string_view<T> b);
#endif
}
void foo()
{
tst::string<char, int> s1;
tst::string_view<char> sv;
if (s1 == sv) {}
if (sv == s1) {}
}
int main()
{
std::string s1;
std::string_view sv;
if (s1 == sv) {}
if (sv == s1) {}
return 0;
}
It compiles when WORKAROUND
is defined as it is very well described here. However, looking into the sources of Clang and MSVC I don't see that this workaround is used by the library. The operator is defined outside of the class simply as:
template<class _CharT, class _Traits>
_LIBCPP_CONSTEXPR_SINCE_CXX14 _LIBCPP_INLINE_VISIBILITY
bool operator==(basic_string_view<_CharT, _Traits> __lhs,
basic_string_view<_CharT, _Traits> __rhs) _NOEXCEPT
{
if (__lhs.size() != __rhs.size()) return false;
return __lhs.compare(__rhs) == 0;
}
See github.
So my question is how does it work for STL?
If you look carefully in the libc++ code there is a workaround present in the code, just after the normal operator is defined there is a second version which is decorated with __type_identity_t
:
template<class _CharT, class _Traits, int = 1>
_LIBCPP_CONSTEXPR_SINCE_CXX14 _LIBCPP_INLINE_VISIBILITY
bool operator==(basic_string_view<_CharT, _Traits> __lhs,
__type_identity_t<basic_string_view<_CharT, _Traits> > __rhs) _NOEXCEPT
Adding the C++20 equivalent std::type_identity
to your code resolves the problem:
template<class T>
bool operator==( string_view<T> a, std::type_identity_t<string_view<T>> b);
https://godbolt.org/z/86r6c8s1h
As explained in the post you linked to, conversions aren't considered when deducing template types, std::type_identity
works around this problem.
Cppreference does mention that this is necessary but not exactly how it should be implemented:
The implementation shall provide sufficient additional constexpr and noexcept overloads of these functions so that a
basic_string_view<CharT,Traits>
objectsv
may be compared to another objectt
with an implicit conversion tobasic_string_view<CharT,Traits>
, with semantics identical to comparingsv
andbasic_string_view<CharT,Traits>(t)