mardi 29 mars 2022

C++ user-defined conversions, ref-qualifiers and overload resolution

Please, help me understand what's wrong with this piece of code:

#include <string>
#include <utility>

class Sample
{
public:
    explicit Sample(std::string data):
        _data(std::move(data))
    {}

    operator const std::string &() const & { return _data; }
    operator std::string &&() && { return std::move(_data); }

private:
    std::string _data;
};

int main()
{
    auto sample1 = Sample{"a"};
    const auto sample2 = Sample{"b"};
    auto sample3 = Sample{"c"};

    auto s1 = std::string{sample1};
    auto s2 = std::string{sample2};
    auto s3 = std::string{std::move(sample3)};

    return 0;
}

The problem is with s3. The compiler says sth like that:

<source>: In function 'int main()':
<source>:27:45: error: call of overloaded 'basic_string(<brace-enclosed initializer list>)' is ambiguous
   27 |     auto s3 = std::string{std::move(sample3)};
      |                                             ^
In file included from /opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/string:55,
                 from <source>:1:
/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/basic_string.h:565:7: note: candidate: 'std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]'
  565 |       basic_string(basic_string&& __str) noexcept
      |       ^~~~~~~~~~~~
/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/basic_string.h:456:7: note: candidate: 'std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]'
  456 |       basic_string(const basic_string& __str)
      |       ^~~~~~~~~~~~
Compiler returned: 1

As I understand, both of the operators for some reason participate in overload resolution for an rvalue. Why is that happening? Should ref-qualifiers leave only one overload for lvalue and the other for rvalue? Am I missing something?

A few other notes:

  1. The behaviour is different across different compilers/versions. I tested it with godbolt and here is that I found:
    • gcc 8.3 and older - compiles fine
    • gcc 8.4 and newer - fails to compile
    • clang - I couldn't find a version where it compiles
    • MSVC - compiles fine on newest versions
  2. if I refactor operators to normal member functions, it all works as intended

Thank You for Your time, looking forward to any replies :)

Aucun commentaire:

Enregistrer un commentaire