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

前端 未结 3 1463
青春惊慌失措
青春惊慌失措 2020-11-30 12:27

I have stumbled on a weird behavior that I just could not explain at first (see ideone):

#include 
#include 
#include 

        
相关标签:
3条回答
  • 2020-11-30 12:45

    A temporary cannot bind to a reference to non-const formal argument.

    Therefore, the non-member << is not picked up.

    You get the void* version instead.

    C++11 fixes this by adding a non-member rvalue stream inserter function,

    C++11
    §27.7.3.9 Rvalue stream insertion
    [ostream.rvalue]
    template <class charT, class traits, class T>
    basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>&& os, const T& x);

    1 Effects: os << x
    2 Returns: os

    0 讨论(0)
  • 2020-11-30 12:47

    To get the started, the simplest solution is to get the list of possible overloads that the compiler considered, for example trying this:

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

    where X is a type without any overload for streaming which yields the following list of possible overloads:

    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>*)
    

    First scanning this list, we can remark that char const* is conspiscuously absent, and therefore it is logical that void const* will be selected instead and thus the address printed.

    On a second glance, we note that all overloads are methods, and that not a single free function appears here.

    The issue is a problem of reference binding: because a temporary cannot bind to a reference to non-const, overloads of the form std::ostream& operator<<(std::ostream&,X) are rejected outright and only member functions remain.

    It is, as far as I am concerned, a design bug in C++, after all we are executing a mutating member function on a temporary, and this requires a (hidden) reference to the object :x

    The workaround, once you understood what went awry, is relatively simple and only requires a small wrapper:

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

    Which prints the expected result.

    0 讨论(0)
  • 2020-11-30 13:00

    The expressionstd::ostringstream() creates a temporary, and operator<< which takes const char* as argument is a free function, but this free function cannot be called on a temporary, as the type of the first parameter of the function is std::ostream& which cannot be bound to temporary object.

    Having said that, <<std::ostringstream() << "some data" resolves to a call to a member function which is overloaded for void* which prints the address. Note that a member function can be invoked on the temporary.

    In order to call the free function, you need to convert temporary (which is rvalue) into a lvalue, and here is one trick that you can do:

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

    That is, std::ostringstream().flush() returns std::ostream& which means, now the free function can called, passing the returned reference as first argument.

    Also, you don't need to use dynamic_cast here (which is slow, as it is done at runtime), for the type of the object is pretty much known, and so you can use static_cast (which is fast as it is done at compile-time):

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

    which should work just fine.

    0 讨论(0)
提交回复
热议问题