Basile
Basile

Reputation: 33

"no match" and "cannot bind lvalue" errors while overloading `operator<<` with `std::wostream` and `std::string`

I've got an error while overloading std::wostream::operator<<() for std::string. Here is the minimal test case illustrating my problem:

#include <string>
#include <sstream>

inline std::wostream &operator<<(std::wostream &os, const std::string &)
{
    return os;
}

class FakeOstream{};

namespace mynamespace {

class FakeClasse1 {
    friend inline FakeOstream &operator<<(FakeOstream &out, const FakeClasse1 &) { 
        return out; 
    }
};

class FakeClasse2 {
    friend inline FakeOstream &operator<<(FakeOstream &out, const FakeClasse2 &)  { 
        return out; 
    }
};

void test()
{
    auto mystring = std::string{u8"mystring"};
    std::wostringstream s;
    s << mystring; // The errors occur here
}

} // namespace mynamespace

The code can be compiled and executed here: http://cpp.sh/9emtv

As you can see here, there is an overload for operator<< with std::wostream and std::string. The two fake classes are empty apart from the declaration of an operator<< with FakeOstream and themselves. The test() function instantiate an std::wostringstream and feed it a std::string. The fake fake classes and test function are in a namespace.

This code yields the following error on cpp.sh at the line s << mystring;:

 In function 'void mynamespace::test()':
25:10: error: cannot bind 'std::basic_ostream<wchar_t>' lvalue to 'std::basic_ostream<wchar_t>&&'
In file included from /usr/include/c++/4.9/istream:39:0,
                 from /usr/include/c++/4.9/sstream:38,
                 from 2:
/usr/include/c++/4.9/ostream:602:5: note: initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = wchar_t; _Traits = std::char_traits<wchar_t>; _Tp = std::basic_string<char>]'
     operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
     ^

When using directly g++ (version 5.3.0 from MSYS2), a no match error is also displayed:

./tmpbug.cpp: In function 'void mynamespace::test()':
./tmpbug.cpp:25:7: error: no match for 'operator<<' (operand types are 'std::wostringstream {aka std::__cxx11::basic_ostringstream<wchar_t>}' and 'std::__cxx11::basic_string<char>')
     s << mystring;
       ^
In file included from C:/Appli/msys64/mingw64/include/c++/5.3.0/istream:39:0,
                 from C:/Appli/msys64/mingw64/include/c++/5.3.0/sstream:38,
                 from ./tmpbug.cpp:2:
C:/Appli/msys64/mingw64/include/c++/5.3.0/ostream:628:5: note: candidate: std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = wchar_t; _Traits = std::char_traits<wchar_t>; _Tp = std::__cxx11::basic_string<char>] <near match>
     operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
     ^
C:/Appli/msys64/mingw64/include/c++/5.3.0/ostream:628:5: note:   conversion of argument 1 would be ill-formed:
./tmpbug.cpp:25:10: error: cannot bind 'std::basic_ostream<wchar_t>' lvalue to 'std::basic_ostream<wchar_t>&&'
     s << mystring;
          ^
In file included from C:/Appli/msys64/mingw64/include/c++/5.3.0/istream:39:0,
                 from C:/Appli/msys64/mingw64/include/c++/5.3.0/sstream:38,
                 from ./tmpbug.cpp:2:

As far as I know, all the parts of the example are necessary for the errors to appear. If I comment out the namespace, the fake classes or just one of the operator<< in the fake classes, the code compile just fine. Moreover, if I just move one of the fake classes or the test function outside of the namespace, the code will also compile just fine.

Additionnally, I tried compiling this example on clang 3.7 by using the compiler from http://cppreference.com, and the code seems to compile without problems.

Is there a problem with my code or is this a GCC bug ? If this is a GCC bug, is there a workaround ?

Upvotes: 3

Views: 560

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275730

This is a bad idea:

inline std::wostream &operator<<(std::wostream &os, const std::string &)

as you should not overload operators on two types in std that do not depend on your own (outside of std or build-in) types. Doing ... doesn't work well. And, in my opinion, shouldn't be allowed.

Regardless, you can generate the same problem with conforming code by simply creating your own namespace notstd and own type notstd::string, then in the global root namespace defining

inline std::wostream &operator<<(std::wostream &os, const notstd::string &)
{
  return os;
}

and get the same symptoms. So that doesn't matter much.


Operators are found first via unqualified name lookup, then via argument dependent lookup.

As we have no using statement, unqualified name lookup first looks in the enclosing namespace. If nothing is found, the namespaces containing it (and eventually the file/global namespace) are then searched.

ADL then augments this with operators found via ADL or Koenig lookup -- it looks in the namespaces of the arguments and their template parameters.

Now, the friend operator<< you defined do live in the namespace their class contains, but they are usually difficult to find.

Somehow your double-declaration of friend operator<< is making your code find them, and stop looking into the global namespace for a <<.

To me this looks like a bug. Neither of those "Koenig operators" should be visible to bog-standard unqualified name lookup with types unrelated to the classes they are "contained" in.

MCVE:

#include <iostream>
#include <sstream>

namespace notstd {
  struct string {};
}

inline void operator<<(std::wostream &os, const notstd::string &){ return; }

class FakeOstream{};

namespace mynamespace {

  class UnusedClass1 {
    friend inline void operator<<(FakeOstream &out, const UnusedClass1 &) { return; }
  };

  class UnusedClass2 {
      // comment this line out and the code compiles:
    friend inline void operator<<(FakeOstream &out, const UnusedClass2 &) { return; }
  };

  void test() {
    auto mystring = notstd::string{};
    std::wostringstream s;
    s << mystring; // The errors occur here
  }

} // namespace mynamespace

int main(){}

live example.

@T.C. found what appears to be this bug being fixed:

test code

fix in gcc

Upvotes: 3

Related Questions