Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices on making c++ class "Showable" (string, ostream)

Tags:

c++

c++11

c++14

I have a value-semantic class that I'd like to be "showable" in the same sense as Haskells Show class, or Python provides a universal __str__() function.

In c++:

  • I could overload operator<<(ostream&, ...) so I can output my class to e.g. cout
  • I could overload operator std::string() so my class converts to std::string
  • I could overload operator const char*() so my class converts to const char *.
  • I could write a str() member, or a to_string(...) free function

Each of these functions could be defined in terms of the other. Is one option better over the others? Are these all the options? What would the most elegant way be to do this given c++11/14/17?

like image 228
Stijn Frishert Avatar asked Oct 21 '15 21:10

Stijn Frishert


2 Answers

The question is going to be put in hold in a very few minutes, but I will still share my thoughts here.

First, of all, we can remove const char* / std::string() operator overloads from the list. If the class is not a string, it should not be convertible to string, and serialization enablement does not make your class a string.

As for str(), to_string and operator << they a pretty equivalent. However, since for any complex class to_string() and str() are very likely to use ostreams internally, I believe, operator << is the best option.

like image 171
SergeyA Avatar answered Nov 15 '22 19:11

SergeyA


I don't know whether it's best practice or not but...

  1. for debugging I always define operator<< which gives a summarised output in text form (this means it's easy to stream objects to log files)

  2. for formatted output I would probably choose to implement this in terms of free functions: detail::x_detail_writer write_details(const X&) and then give detail::x_detail_writer (which is a functor) an operator<< overload

  3. for anything but the most trivial object I implement to_string in terms of operator<< if at all.

for aiding with debugging output we have a helper class which goes something like this:

template<class T>
struct make_writeable {
  friend std::ostream& operator<<(std::ostream& os, const T& t) {
    // example prefix...
    os << demangle(typeid(T).name()) << "{";
    t.write(os);
    // example postfix:
    os << " }";
    return os;
  }
};

then derive some class from this and give it a member function called write:

struct X : make_writeable<X>
{
  void write(std::ostream& os) const {
    // write out members here. they will appear within the prefix and postfix
  }
};
like image 32
Richard Hodges Avatar answered Nov 15 '22 18:11

Richard Hodges