Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to print instances of my classes

32 views
Skip to first unread message

Steve Keller

unread,
Nov 6, 2018, 12:11:49 PM11/6/18
to
What is the best and most standard way to print objects of your
classes? In other people's code I often find something like this:

class Foo {
public:
std::string toString() const {
std::ostringstream s;
s << "{..." << /* more items */ << "}";
return s.str();
};

class MyEnum {
enum { A, B, C } e;
public:
std::string toString() const {
switch (e) {
case A:
return "A";
case B:
return "B";
case C:
return "C";
}
};

void some_other_func(const Foo &f, const MyEnum &e) {
std::cout << f.toString() << ' '
<< e.toString() << std::endl;
}

However, I prefer to implement functions that print to std::ostream
directly, instead of writing to std::stringstream, then creating a
string, and then printing the string to an ostream:

class Foo {
public:
void print(std::ostream &os) const {
os << "{..." << /* more items */ << "}";
}
};

std::ostream &operator<<(std::ostream &os, const Foo &foo) {
foo.print(os);
return os;
}

class MyEnum {
enum { A, B, C } e;
public:
void print(std::ostream &os) const {
switch (e) {
case A:
os << "A"; break;
case B:
os << "B"; break;
case C:
os << "C"; break;
}
};

std::ostream &operator<<(std::ostream &os, const MyEnum &e) {
e.print(os);
return os;
}

void some_other_func(const Foo &f, const MyEnum &e) {
std::cout << f << ' ' << e << std::endl;
}

Since I see the first method so often I wonder if there is something wrong
with my solution. Does the above approach have any advantages I don't see?

Steve

Paavo Helde

unread,
Nov 6, 2018, 12:57:23 PM11/6/18
to
On 6.11.2018 19:11, Steve Keller wrote:
> What is the best and most standard way to print objects of your
> classes? In other people's code I often find something like this:
>
> class Foo {
> public:
> std::string toString() const {
>
> However, I prefer to implement functions that print to std::ostream
> directly,

The stream approach is architecturally more sound, more flexible and
more customizable. For example, if your object "stringification"
involves floating-point numbers then the toString() function has to
decide how to format the decimal point: by the current locale, always as
'.', etc. When using the stream approach, the client code is assumed to
know what it wants and to imbue the needed locale to the stream.

At least this is the intention. In practice the customization
possibilities provided by locales are often far too few and too little
to provide anything better than inconsistencies and grammar errors, so
I'm not sure if they do more good than harm.

The strings approach typically avoids all this flexibility and
customizations. This means the result is always in fixed format and the
performance is usually better (string+= won up to 2x in my measurements,
compared to ostringstream<<).

So the answer depends on your needs and preferences. Is the printout
meant for humans or for other software? If for humans, is it for a
single person or for many? Is the speed important? Is fixed format
important? Etc.

Jorgen Grahn

unread,
Nov 6, 2018, 2:48:41 PM11/6/18
to
On Tue, 2018-11-06, Steve Keller wrote:
> What is the best and most standard way to print objects of your
> classes? In other people's code I often find something like this:
>
> class Foo {
> public:
> std::string toString() const {
> std::ostringstream s;
> s << "{..." << /* more items */ << "}";
> return s.str();
> };
...
> void some_other_func(const Foo &f, const MyEnum &e) {
> std::cout << f.toString() << ' '
> << e.toString() << std::endl;
> }
>
> However, I prefer to implement functions that print to std::ostream
> directly, instead of writing to std::stringstream, then creating a
> string, and then printing the string to an ostream:
...

> Since I see the first method so often I wonder if there is something wrong
> with my solution. Does the above approach have any advantages I don't see?

I suspect the first approach is a Java/whatever other language
influence. To me it means working against the intention of the
language (but I don't see it much).

/Jorgen

--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .

Alf P. Steinbach

unread,
Nov 6, 2018, 3:33:24 PM11/6/18
to
On 06.11.2018 18:11, Steve Keller wrote:
> What is the best and most standard way to print objects of your
> classes? In other people's code I often find something like this:
>
> class Foo {
> public:
> std::string toString() const {
> std::ostringstream s;
> s << "{..." << /* more items */ << "}";
> return s.str();
> };

This code suffers from non-systematic indentation. Also, please use
spaces, not tabs. The presentation of tabs depends on the software used
to view the message, so tabs can really mess up things.

From the string format you've chosen it appears that the purpose of
"print objects of your classes" is tracing and debugging, not
interaction with a user or storing a description from which an equal
object can be reconstituted (serialization and de-serialization).

For tracing and debugging involving an iostream's locale machinery and
general inefficiency, is IMHO counter-productive.

One could build on `std::to_string`, but that involves the C locale
machinery and forced dynamic allocations for the string buffers. Not
quite as bad as the C++ level, but results that depend on a global
easily modified state, means unpredictable results in general.

Happily, C++17 introduced ¹`to_chars`. Unfortunately, (1) there's
apparently no way to predict the required buffer size, except by using
`snprintf`, the design or possibly (but not likely) just its
documentation, is bungled, and (2) the g++'s standard library
implementation libstdc++ ²doesn't yet support `to_chars`.

It's not that big a deal to define that kind of functionality, and there
are 3rd party libraries that do, but I think I would assume a locale
that at worst uses mainland European "," instead of ".", and /under that
assumption/ accept the slight performance hit from the C locale stuff
(essentially, replacing any produced ",") and just use `snprintf`:

-------------------------------------------------------------------------
#include <algorithm> // std::replace
#include <iterator> // std::size
#include <string> // std::string
#include <stddef.h> // ptrdiff_t

namespace cppx
{
template< class A_type > using Type_ = A_type;

using Size = ptrdiff_t;

template< class Container >
auto n_items( const Container& c )
-> Size
{ return std::size( c ); }
} // namespace cppx

namespace my
{
using cppx::n_items, cppx::Type_, cppx::Size;
using std::string, std::replace;

// Always returns the number of chars that /would/ be generated on
success.
auto convert_to_text(
const double x,
const Type_<char*> buffer,
const Size buffer_size
)
-> Size
{
const Size n_chars = snprintf( buffer, buffer_size, "%g", x );
if( n_chars <= buffer_size )
{
replace( buffer, buffer + n_chars, ',', '.' );
}
return n_chars;
}

auto to_string( const double x, string buffer = string( 16, '\0' ) )
-> string
{
const Size n_chars = convert_to_text( x, buffer.data(),
n_items( buffer ) );
if( n_chars > n_items( buffer ) )
{
buffer.resize( n_chars );
convert_to_text( x, buffer.data(), n_items( buffer ) );
}
return buffer;
}

} // namespace my

#include <iostream>
auto main()
-> int
{
using namespace std;
cout << my::to_string( 3.14e15 ) << endl;
}
-------------------------------------------------------------------------

It shouldn't be difficult to add support for other formats, field width,
precision and whatever.


> class MyEnum {
> enum { A, B, C } e;
> public:
> std::string toString() const {
> switch (e) {
> case A:
> return "A";
> case B:
> return "B";
> case C:
> return "C";
> }
> };

I guess the intent is to somehow provide client code with three global
static instances of `MyEnum`, corresponding to the three `enum` values.

Instead of a `switch`, just use an array of values.

auto to_string() const
-> const char*
{
static const Type_<const char*> names[] = {"A", "B", C"};
return names[e];
}

By the way, using all uppercase for values runs the risk of inadvertent
text substitution. In C and C++ you can avoid needless work and needless
annoyance by reserving all uppercase identifiers for macros -- and the
few idiomatic other uses such as `T` for template parameter type.


> void some_other_func(const Foo &f, const MyEnum &e) {
> std::cout << f.toString() << ' '
> << e.toString() << std::endl;
> }
>
> However, I prefer to implement functions that print to std::ostream
> directly, instead of writing to std::stringstream, then creating a
> string, and then printing the string to an ostream:

It's unclear what the "instead of" refers to.

You haven't illustrated any such code.


> class Foo {
> public:
> void print(std::ostream &os) const {
> os << "{..." << /* more items */ << "}";
> }
> };
>
> std::ostream &operator<<(std::ostream &os, const Foo &foo) {
> foo.print(os);
> return os;
> }

This is the conventional way to do things.

It scales, it's reasonably type safe, but it's in practice inefficient
/and/ it doesn't handle nationalization well. Better with format
strings. Check out e.g. the Boost Format library.


> class MyEnum {
> enum { A, B, C } e;
> public:
> void print(std::ostream &os) const {
> switch (e) {
> case A:
> os << "A"; break;
> case B:
> os << "B"; break;
> case C:
> os << "C"; break;
> }
> };

Again, better use an array than a `switch`.

Note that this approach is good only where the client code wants the
text output to a stream. For that case it's just about as efficient and
clean to use as the `const char*` function I presented above.

For any other use it drags in inefficiency and makes the usage unclean.


> std::ostream &operator<<(std::ostream &os, const MyEnum &e) {
> e.print(os);
> return os;
> }
>
> void some_other_func(const Foo &f, const MyEnum &e) {
> std::cout << f << ' ' << e << std::endl;
> }
>
> Since I see the first method so often I wonder if there is something wrong
> with my solution. Does the above approach have any advantages I don't see?

Uhm, don't know.

But regarding the "so often", IMO that indicates that you are exposed to
code that is not typical.


Cheers!,

- Alf

Notes:
¹ https://en.cppreference.com/w/cpp/utility/to_chars
²
https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html#status.iso.2017

red floyd

unread,
Nov 6, 2018, 5:09:14 PM11/6/18
to
On 11/6/2018 12:33 PM, Alf P. Steinbach wrote:
> On 06.11.2018 18:11, Steve Keller wrote:
------------
>
> It shouldn't be difficult to add support for other formats, field width,
> precision and whatever.
>
>
>>     class MyEnum {
>>         enum { A, B, C } e;
>>     public:
>>     std::string toString() const {
>>         switch (e) {
>>         case A:
>>             return "A";
>>         case B:
>>             return "B";
>>         case C:
>>             return "C";
>>         }
>>     };
>
> I guess the intent is to somehow provide client code with three global
> static instances of `MyEnum`, corresponding to the three `enum` values.
>
> Instead of a `switch`, just use an array of values.
>
>     auto to_string() const
>         -> const char*
>     {
>         static const Type_<const char*> names[] = {"A", "B", C"};
>         return names[e];
>     }
>

In this individual case, yes, an array works. But remember, enums
do not need to be consecutive, in which case some other mechanism
is required. One size does not fit all.



Jorgen Grahn

unread,
Nov 7, 2018, 1:46:28 AM11/7/18
to
On Tue, 2018-11-06, Stefan Ram wrote:
> Steve Keller <kel...@no.invalid> writes:
>>What is the best and most standard way to print objects of your
>>classes?
>
> These actually are two questions:
>
> - What is the best /interface/ your class should
> support to print its object? and
>
> - What is the best /implementation/ of this interface?
>
> With regard to the interface, it was, IIRC, Herb Sutter wh
> who suggested something like:
>
> class example
> { ~~~
>
> public:
> ::std::ostream & print( ::std::ostream & stream )const
> { ~~~ }
>
> ~~~ }
>
> inline ::std::ostream& operator <<
> ( ::std::ostream & stream, example const & object )
> { return object.print( stream ); }

I think it's in The C++ Programming Language, except "print" is called
"put". That's the pattern I follow (is there really any alternative)?

If I can implement it all in operator<< I do so, but that's pretty
rare except for thin wrapper classes where printing foo is the same as
printing foo.value().
0 new messages