Подтвердить что ты не робот

Std:: ostringstream печатает адрес c-строки вместо своего содержимого

Я наткнулся на странное поведение, которое я просто не мог объяснить сначала (см. 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

Удивительно, но в последнем акте мы имеем адрес, а не контент!

Почему это так?

4b9b3361

Ответ 1

Выражение std::ostringstream() создает временное значение и operator<<, которое принимает const char*, поскольку аргумент является свободной функцией, но эта свободная функция не может быть вызвана временным образом, так как тип первого параметра функции std::ostream&, который не может быть привязан к временному объекту.

Сказав, что <<std::ostringstream() << "some data" разрешает вызов функции-члена, которая перегружена для void*, которая печатает адрес. Обратите внимание, что функция-член может быть вызвана во временное.

Чтобы вызвать свободную функцию, вам нужно преобразовать временную (которая является rvalue) в lvalue, и вот один трюк, который вы можете сделать:

 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";

который должен работать нормально.

Ответ 2

Временное не может связываться с ссылкой на неконстантный формальный аргумент.

Следовательно, не-член << не подбирается.

Вместо этого вы получаете версию void*.

С++ 11 исправляет это, добавляя функцию не-член rvalue streamer,

С++ 11
§27.7.3.9 Вставка потока Rvalue
[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

Приветствия и hth.

Ответ 3

Чтобы начать работу, самым простым решением является получение списка возможных перегрузок, которые рассматривался компилятором, например пример:

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* и, следовательно, адрес напечатан.

На второй взгляд отметим, что все перегрузки являются методами и что здесь не появляется ни одна свободная функция.

Проблема связана с привязкой ссылок: поскольку временное не может привязываться к ссылке на неконстантную, перегрузки формы std::ostream& operator<<(std::ostream&,X) отклоняются напрямую и остаются только функции-члены.

Это, насколько мне известно, ошибка проектирования на С++, ведь мы выполняем мутирующую функцию-член во временном, и для этого требуется (скрытая) ссылка на объект: 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";

Что печатает ожидаемый результат.