How to overload operator<<(double const&) in an inherited std::stringstream?

561 Views Asked by At

I would like to overwrite the operator<< such that:

double d = 3.0;
mycustomstringstream << "Hello World " << d << "what a nice day.";
std::cout << mystream.str() << std::endl;

will produce the output:

Hello World (double)(3.00000000000)what a nice day.

Remark to avoid confusion: Every double shall be printed this way then, with a "(double)(" in front and a ")" behind, whenever a stream implementing my operator is used. The stream should come at least with all the functionality of string-stream, and I prefer to not have to fully re-implement the streamby myself. The purpose of the question is not for exercise but to save time/tediousness/errors from otherwise having to wrap a function call double_to_string around every streamed double d.

What I tried

Overriding

Since I would be fine for doubles to be printed like so, I boldly implemented:

std::ostream& operator<<(std::ostream& o, double const& d){
  o<<"(double)("<< d << ")";
  return o;
}

That does not work because the compiler explains about ambiguity (since that operator is already defined).

Inheriting

So as in the beginning, I am fine to inherit from std::stringstream and just replace the definition of that operator for my customstringstream:

#include <sstream>

class MyCustomStringStream: public std::stringstream{};

MyCustomStringStream& operator<<(MyCustomStringStream& o, double const& d){
  o<<"(double)("<< ( (std::stringstream)(o) << d ) << ")";
  return o;
}

Here is the error:

error: use of deleted function 'std::__cxx11::basic_stringstream<_CharT, _Traits, _Alloc>::basic_stringstream(const std::__cxx11::basic_stringstream<_CharT, _Traits, _Alloc>&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]'

So then instead:

#include <iostream>
#include <sstream>

class MyStringStream: public std::stringstream{
    std::stringstream aux;
public:
    MyStringStream& operator<<(double const& d){
        aux.str() = "";
        aux << std::scientific << "((double)(" << d << ")";
        *this << aux.str();
        return *this;
    }
};

int main() {
    double d = 12.3;
    MyStringStream s;
    s << "Hello World " << d << "what a nice day.";
    std::cout << s.str() << std::endl;
}

But this still prints:

Hello World 12.3what a nice day.

Demo

5

There are 5 best solutions below

10
Jerry Coffin On BEST ANSWER

Streams are designed to support this sort of customization, though they way they support it may not initially be as obvious as you'd like (and implementing it correctly is kind of a pain too).

When you write a number to a stream, the formatting is delegated to the stream's locale. That locale contains (among other things) a num_put facet, which is what actually formats a number as desired for the stream.

The num_put facet has overloads of do_put for each of the standard numeric types, including double. Since you (apparently) want to leave other types formatted normally, and apply your special formatting only to doubles, you override only the do_put overload for double.

To format a number with this, you imbue your stream with a locale that contains an instance of that num_put facet:

  std::ostringstream os;

  std::locale loc(std::locale::classic(), new my_num_put<char>);

  os.imbue(loc);

Then write a double to the stream:

  os << 123.456 << "\n";

  // and copy the result to standard output so we can see it:
  std::cout << os.str();

For a proof of concept, your do_put can use a stringstream to convert the double to a string, then copy the prefix, string, and suffix to the output sequence. Moving beyond proof of concept is a bit more work though.

To format the number correctly, we need to get all the formatting information from the ios_base that's passed to do_put, and set the formatting for our temporary stringstream accordingly. For example:

    std::ostringstream temp;

    // copy flags, width and precision:
    temp.flags(b.flags());
    temp.width(b.width());
    temp.precision(b.precision());
    
    temp << v;

Figuring out everything that needs to be copied over to do all formatting correctly is left as an exercise for the interested reader. Here's a quick test program with semi-complete code as described above:

#include <locale>
#include <sstream>
#include <ios>
#include <limits>
#include <iostream>
#include <iomanip>

template <class charT, class OutputIterator = std::ostreambuf_iterator<charT>>
class my_num_put : public std::num_put<charT, OutputIterator> {
public:
  using iter_type = typename std::num_put<charT, OutputIterator>::iter_type;
  using char_type = typename std::num_put<charT, OutputIterator>::char_type;

  iter_type do_put(iter_type i, std::ios_base &b, char_type fill, double v) const override {
    std::string prefix = "(double)(";
    std::string suffix = ")";

    std::ostringstream temp;
    temp.flags(b.flags());
    temp.width(b.width());
    temp.precision(b.precision());
    temp.fill(fill);
    
    temp << v;
    std::string const &s = temp.str();

    std::copy(prefix.begin(), prefix.end(), i);
    std::copy(s.begin(), s.end(), i);
    std::copy(suffix.begin(), suffix.end(), i);
    return i;
  }
};

int main() {

  std::ostringstream os;

  std::locale loc(std::locale::classic(), new my_num_put<char>);

  os.imbue(loc);

  // test a minimal case:
  os << 1.234 << "\n";
 
  // test with a few formatting flags:
  os << std::setw(17) << 123.456 << "\n";
  os << std::setw(14) << std::setprecision(12) << std::left << 9876.54321 << "\n";
  std::cout << os.str();
}

The result I get is about as you'd apparently hope/expect:

(double)(1.234)
(double)(          123.456)                
(double)(9876.54321    )

This should work with essentially any stream, not just a stringstream. And once you've imbued a stream with this locale, you don't have to make any further changes to other code for all doubles you write to that stream to be formatted this way.

For the moment, I've left the precision and width applying only to the width and precision used for the number itself, so when you write something out with std::setw(10), the output will be 10 + the size of prefix and suffix you use. Depending on your viewpoint, it may well make more sense to subtract the size of your prefix+suffix from the width applied to the number itself, so the width will be the total field size, not just the size of the number part.

So, while I'd still consider it a proof of concept rather than finished, fully-tested code, I'd say this does give a basic idea of an approach that should be able to accomplish your goal (and depending on how lofty a goal you've setting, may already be adequate).

12
Pepijn Kramer On

Whatever you think about inheritance it is usually not the first solution you should think of. I would not derive from a stream anyway (usually inheriting from STL types is not recommended, though streams allow for this).

There is a more reusable solution for your situation - make a helper formatting object (demo).

At least with this (C++17) approach, you can still mix normal and scientific formatting (and still have a central point in your code where you can manage what your formatting should look like):

#include <iostream>
#include <sstream>

template<typename type_t>
struct as_scientific
{
    explicit as_scientific(type_t& v) : value{v}{};
    type_t value;
};

template<typename type_t>
std::ostream& operator<<(std::ostream& os, const as_scientific<type_t>& scientific)
{
    os << std::scientific << "((" << typeid(type_t).name() << ")(" <<  scientific.value << ")";
    return os;
}

int main() 
{
    double d = 12.3;
    std::ostringstream s;
    s << "Hello World " << as_scientific{d} << "what a nice day.";
    std::cout << s.str() << "\n";

    // or directly
    std::cout << as_scientific{d} << "\n";
}

For C++20, there is the <format> library, which can help you with custom for matters.

7
GRamon On

Quoting @jbk 's comment:

It doesn't work because the << "Hello World" part is not using code in your class, but is instead using the base class's (std::stringstream') overload of operator << for const char *. That function returns a reference to itself, not to your derived class. Because the base class returned a reference to a std::stringstream (again, not an object of MyStringStream), it is that object's overload of operator<< for double that is called. If you try using just s << d << "what a nice day you will see that your overload is called. Does that make sense?

So then logically the solution is as follows:

#include <iostream>
#include <sstream>

class MyStringStream: public std::stringstream{
    std::stringstream aux;
public:
    MyStringStream& operator<<(double const& d){
        aux.str() = "";
        aux << std::scientific << "((double)(" << d << ")";
        *this << aux.str();
        return *this;
    }
    //
    template<typename T>
    MyStringStream& operator<<(T const& t){
        static_cast<std::stringstream&>(*this) << t;
        return *this;
    }
};

int main() {
    double d = 12.3;
    MyStringStream s;
    s << "Hello World " << d << "what a nice day.";
    std::cout << s.str() << std::endl;
}
0
n. m. could be an AI On

std::basic_ostream<C,T>& operator<<( double value ) is a non-virtual member function of std::basic_ostream<C,T>.

  • The concept of overriding only applies to virtual member functions.
  • You cannot overload a member function of a class that you don't own with another member function.
  • You can overload a member function with a non-member function, but in your case it will be ambiguous because the arguments are exactly the same.
  • You can inherit and overload for an inherited class (with a member or a non-member function), but due to the chaining of operator<<, your function will only be selected if your double is the first thing you output. If you do mystream << 3.0 you will see the desired result. However if you do something like mystream << "Hello" << 3.0, then (mystream << "Hello") returns ostream&, and the ostream member function is selected. To make your code work, you would need to implement all overloads of operator<< for your class (member and non-member) and make them all return a reference to your class. This can be done with a catch-all template, but catch-all templates are dangerous. And it won't work anyway if you need to pass your stream down to some function with a name different from operator<<.

So you can see that what you want to do is not feasible. Almost can be done, but not quite.

2
James Kanze On

ostream isn't designed to be customized in that way. Although it can (and often is, including in the standard) derived from, all the derivations do is initialize it with a different streambuf, and possibly add functions to access any additional functionality in the streambuf. You cannot realistically change the meaning of operator<< for the types provided by the standard, even when they are broken (for example, std::complex). Streams are defined to be customized by derivation only with regards to the data sink or source (streambuf). You can add operator<< for user defined types, but you cannot change the meaning of that operator for the types for which the standard defines it. (This is much the same philosophy as for operator overloading in general. You can extend the language to handle your types, but you cannot change the behavior of types defined by the standard.)