【问题标题】:"no match" and "cannot bind lvalue" errors while overloading `operator<<` with `std::wostream` and `std::string`使用 `std::wostream` 和 `std::string` 重载 `operator<<` 时出现“不匹配”和“无法绑定左值”错误
【发布时间】:2016-03-29 13:54:47
【问题描述】:

在为std::string 重载std::wostream::operator&lt;&lt;() 时出错。 这是说明我的问题的最小测试用例:

#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

代码可以在这里编译执行:http://cpp.sh/9emtv

正如您在此处看到的,operator&lt;&lt;std::wostream 存在重载 和std::string。除了声明之外,这两个假类是空的 operator&lt;&lt;FakeOstream 和他们自己。 test() 函数 实例化一个std::wostringstream 并提供一个std::string。假的 假类和测试函数位于命名空间中。

此代码在 cpp.sh 的 s &lt;&lt; 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)
     ^

当直接使用 g++(来自 MSYS2 的 5.3.0 版)时,no match error 也是 显示:

./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:

据我所知,示例的所有部分都是错误发生所必需的 出现。如果我注释掉命名空间、假类或只是其中之一 operator&lt;&lt; 在假类中,代码编译得很好。此外,如果我 只需将其中一个假类或测试函数移到命名空间之外, 代码也可以正常编译。

此外,我尝试使用编译器在 clang 3.7 上编译此示例 来自http://cppreference.com,代码似乎编译没有问题。

我的代码有问题还是 GCC 错误?如果这是一个 GCC 错误,是 有解决办法吗?

【问题讨论】:

  • 如果上面的代码可以编译,那么什么代码会产生错误?
  • 代码无法编译,至少在 GCC 上是这样。错误发生在s &lt;&lt; mystring; 行上。我已编辑问题以添加此精度。
  • 对我来说看起来像是一个名称查找问题,但我猜不出正确的行为是什么。将using ::operator&lt;&lt;; 添加到test 是我能找到的最小解决方法。 (将重载添加到std 以启用依赖于参数的查找也会使问题消失,但我不确定它的有效性。)
  • 谢谢,using ::operator&lt;&lt; 似乎工作正常 :)。不过,我想知道为什么会出现这些错误,至少想知道我是否必须提交 GCC 错误。
  • 好像已经在主干上修好了。

标签: c++ c++11 gcc c++14


【解决方案1】:

这是个坏主意:

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

因为您不应该重载 std 中不依赖于您自己(std 或内置)类型的两种类型的运算符。做...效果不好。而且,在我看来,不应该被允许。

无论如何,您可以通过简单地创建自己的命名空间notstd 和自己的类型notstd::string,然后在全局根命名空间定义中生成相同的代码问题

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

并得到相同的症状。所以这并不重要。


首先通过非限定名称查找找到运算符,然后通过参数相关查找。

由于我们没有using 语句,因此非限定名称查找首先在封闭的命名空间中查找。如果没有找到,则搜索包含它的命名空间(最终是文件/全局命名空间)。

然后,ADL 使用通过 ADL 或 Koenig 查找找到的运算符对其进行扩充——它在参数及其模板参数的命名空间中查找。

现在,您定义的friend operator&lt;&lt; do 存在于它们的类包含的命名空间中,但是它们通常很难找到。

不知何故,您对 friend operator&lt;&lt; 的双重声明使您的代码找到它们,并停止在全局命名空间中查找 &lt;&lt;

对我来说,这看起来像是一个错误。这些“Koenig 运算符”都不应该被沼泽标准的非限定名称查找所看到,其类型与它们“包含”在其中的类无关。

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.发现似乎正在修复这个错误:

test code

fix in gcc

【讨论】:

  • 这并不违法;你不能把这个东西放在namespace std,所以这是愚蠢和毫无意义的,但并不违法。
  • @T.C.最后我检查了,除非类型依赖于 std 和/或内置类型之外的东西,否则你不能重载运算符。还是我读错了那个缺陷解决方案?
  • 我们讨论的是哪种缺陷解决方案?
  • @T.C. cplusplus.github.io/LWG/lwg-active.html#2139 -- 我确实看错了。它不会使非程序定义类型上的运算符非法。问题仍然存在。过失
  • @M.M "ADL 仅在未通过非限定查找完全找不到名称时才考虑"这是错误的。
猜你喜欢
  • 2012-08-04
  • 2014-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-28
  • 1970-01-01
  • 2018-03-03
  • 2011-03-13
相关资源
最近更新 更多