ostringstream打印c字符串的地址而不是其内容

std::ostringstream printing the address of the c-string instead of its content

本文关键字:地址 打印 字符串 ostringstream      更新时间:2023-10-16

我偶然发现了一个奇怪的行为,一开始我无法解释(见ideone):

#include <iostream>
#include <sstream>
#include <string>
int main() {
  std::cout << "Reference     : "
            << (void const*)"some data"
            << "n";
  std::ostringstream s;
  s << "some data";
  std::cout << "Regular Syntax: " << s.str() << "n";
  std::ostringstream s2;
  std::cout << "Semi inline   : "
            << static_cast<std::ostringstream&>(s2 << "some data").str()
            << "n";
  std::cout << "Inline        : "
            << dynamic_cast<std::ostringstream&>(
                 std::ostringstream() << "some data"
               ).str()
            << "n";
}

给出输出:

Reference     : 0x804a03d
Regular Syntax: some data
Semi inline   : some data
Inline        : 0x804a03d

令人惊讶的是,在最后一个cast中,我们有地址,而不是内容!

表达式std::ostringstream()创建了一个临时对象,而以const char*为实参的operator<<是一个自由函数,但是这个自由函数不能在临时对象上调用,因为函数的第一个形参的类型是std::ostream&,不能绑定到临时对象上。

话虽如此,<<std::ostringstream() << "some data"解析为对void*重载的成员函数的调用,该函数输出地址。请注意,可以在临时对象上调用成员函数。

为了调用free函数,需要将temporary(右值)转换为左值,这里有一个技巧可以使用:

 std::cout << "Inline        : "
            << dynamic_cast<std::ostringstream&>(
                 std::ostringstream().flush() << "some data"
               ).str()
            << "n";

也就是说,std::ostringstream().flush()返回std::ostream&,这意味着现在可以调用自由函数,将返回的引用作为第一个参数传递。

同样,您不需要在这里使用dynamic_cast(它很慢,因为它是在运行时完成的),因为对象的类型已经非常清楚了,因此您可以使用static_cast(它在编译时完成的速度很快):

 std::cout << "Inline        : "
            << static_cast<std::ostringstream&>(
                 std::ostringstream().flush() << "some data"
               ).str()
            << "n";

临时类型不能绑定到对非const形式实参的引用。

因此,不拾取非成员<<

您将获得void*版本。

c++ 11通过增加一个非成员右值流插入器函数 修复了这个问题。
c++ 11


§27.7.3.9右值流插入
[ostream.rvalue]
template <class charT, class traits, class T>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>&& os, const T& x);

1 效果: os << x
2 返回: os

要开始,最简单的解决方案是获取编译器考虑的可能重载的列表,例如尝试如下:

X x;
std::cout << x << "n";

其中X是一个没有任何流重载的类型,产生以下可能的重载列表:

prog.cpp: In function ‘int main()’:
prog.cpp:21: error: no match for ‘operator<<’ in ‘std::cout << x’
include/ostream:112: note: candidates are: std::ostream& std::ostream::operator<<(std::ostream& (*)(std::ostream&))
include/ostream:121: note:                 std::ostream& std::ostream::operator<<(std::basic_ios<_CharT, _Traits>& (*)(std::basic_ios<_CharT, _Traits>&))
include/ostream:131: note:                 std::ostream& std::ostream::operator<<(std::ios_base& (*)(std::ios_base&))
include/ostream:169: note:                 std::ostream& std::ostream::operator<<(long int)
include/ostream:173: note:                 std::ostream& std::ostream::operator<<(long unsigned int)
include/ostream:177: note:                 std::ostream& std::ostream::operator<<(bool)
include/bits/ostream.tcc:97: note:         std::ostream& std::ostream::operator<<(short int)
include/ostream:184: note:                 std::ostream& std::ostream::operator<<(short unsigned int)
include/bits/ostream.tcc:111: note:        std::ostream& std::ostream::operator<<(int)
include/ostream:195: note:                 std::ostream& std::ostream::operator<<(unsigned int)
include/ostream:204: note:                 std::ostream& std::ostream::operator<<(long long int)
include/ostream:208: note:                 std::ostream& std::ostream::operator<<(long long unsigned int)
include/ostream:213: note:                 std::ostream& std::ostream::operator<<(double)
include/ostream:217: note:                 std::ostream& std::ostream::operator<<(float)
include/ostream:225: note:                 std::ostream& std::ostream::operator<<(long double)
include/ostream:229: note:                 std::ostream& std::ostream::operator<<(const void*)
include/bits/ostream.tcc:125: note:        std::ostream& std::ostream::operator<<(std::basic_streambuf<_CharT, _Traits>*)

首先扫描这个列表,我们可以注意到char const*明显缺席,因此选择void const*并打印地址是合乎逻辑的。

再看一眼,我们注意到所有的重载都是方法,这里没有出现一个自由函数。

问题是引用绑定的问题:因为临时不能绑定到非const的引用,所以形式std::ostream& operator<<(std::ostream&,X)的重载被直接拒绝,只保留成员函数。

在我看来,这是c++中的一个设计错误,毕竟我们是在临时对象上执行一个变异的成员函数,这需要对对象的(隐藏的)引用:x

一旦您了解了出错的地方,解决方法就相对简单,只需要一个小包装器:

struct Streamliner {
  template <typename T>
  Streamliner& operator<<(T const& t) {
    _stream << t;
    return *this;
  }
  std::string str() const { return _stream.str(); }
  std::ostringstream _stream;
};
std::cout << "Inline, take 2: " << (Streamliner() << "some data").str() << "n";

打印预期结果