Search code examples
c++templatestype-conversionstring-view

How does the implicit conversion of string to string_view work for operator== in the standard library?


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?


Solution

  • 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> object sv may be compared to another object t with an implicit conversion to basic_string_view<CharT,Traits>, with semantics identical to comparing sv and basic_string_view<CharT,Traits>(t)