Skip to content
Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

How to print anything in C++ (part 5)

elbeno, 2 February, 201530 June, 2015

Part 1 Part 2 Part 3 Part 4 Part 5 Postscript

So far, we can print containers, but what about arrays? And what about “pretty-printing” strings – perhaps we need to wrap them with quotes. Well, we know that with the existing code, both arrays and strings count as outputtable. Both std::string and char* (and its variously const friends) can be passed to operator<<, and so can arrays, because they decay to pointers.

So, we should be able to deal with this using common-or-garden specialization on stringifier_select. Specializing for std::string is easy:

template 
struct stringifier_select, 
                          is_outputtable_tag>
{
  using S = std::basic_string;
  explicit stringifier_select(const S& t) : m_t(t) {}

  std::ostream& output(std::ostream& s) const
  {
    return s << C('\"') << m_t << C('\"');
  }

  const S& m_t;
};

And specializing for the other char* related types is also as easy, albeit verbose because of dealing with char*, const char*, char* const and const char* const.

Specializing for arrays of char is just as easy. This time we have just the array size as a template argument. And once again, there is a specialization for arrays of const char that is identical.

template 
struct stringifier_select
{
  using S = char[N];
  explicit stringifier_select(const S& t) : m_t(t) {}

  std::ostream& output(std::ostream& s) const
  {
    return s << '\"' << m_t << '\"';
  }

  const S& m_t;
};

Specializing for arrays (other than of char) is also easy, and gives us a chance to abstract out the code that we used for the iterable printing. By happy circumstance (i.e. by design!), arrays support std::begin() and std::end(), so we can write the following:

template 
std::ostream& output_iterable(std::ostream& s, const T& t)
{
  s << iterable_opener()(t);
  auto b = std::begin(t);
  auto e = std::end(t);
  if (b != e)
    s << prettyprint(*b);
  std::for_each(++b, e,
                [&s, &t] (auto& e)
                { s << iterable_separator()(t)
                    << prettyprint(e); });
  return s << iterable_closer()(t);
}

template 
struct stringifier_select
{
  using S = T[N];
  explicit stringifier_select(const S& t) : m_t(t) {}

  std::ostream& output(std::ostream& s) const
  {
    return output_iterable(s, m_t);
  }

  const S& m_t;
};

And the code for printing iterable things changes likewise. Unlike the situation with char*, we don't need to deal with const and non-const separately because here, T itself is inferred to be const or not.

And that's pretty much it - just a couple more things to add. I mentioned enum classes back in part 1, and here's how we print out their values:

template 
struct stringifier_select
{
  explicit stringifier_select(T t) : m_t(t) {}

  std::ostream& output(std::ostream& s) const
  {
    return s << static_cast>(m_t);
  }

  T m_t;
};

Simple. Two final things to add: first, specialize for pretty-printing bool equivalently to using std::boolalpha; second, distinguish a few "unprintable" things and output something for them - classes without operator<<, unions, nullptr. The code that does this is very similar to what we've already seen.

So now, we can pretty-print all "normally" printable things, containers, pairs and tuples, callable things, certain unprintables that we can meaningfully label, and really unprintable things with a fallback. I think that'll do for the time being. It's been a fun journey, exploring TMP techniques, C++ type support, mapping over tuples, and the amazing void_t.

You can find all the resulting code from this exercise on github.

C++ Programming

Post navigation

Previous post
Next post

Related Posts

Thoughts on Default Construction

16 August, 2017

What does default construction mean? Why do we write default constructors? When and why should we require them? I’ve been pondering these questions lately. One of the great things that C++ gets right is that it grants programmers the ability to create types that behave like built-in types. Many languages…

Read More

Lisping towards enlightenment

17 December, 200729 December, 2007

Me: a C/C++ programmer of 12 years (professionally), a Lisp dabbler (programmer is too strong a word to use here) of, let’s say ~3 months. I’ve done enough functional stuff to be comfortable with that side, but I haven’t (hadn’t) yet written a project big enough to get me used…

Read More

Compile-time RNG tricks

15 October, 201518 October, 2015

(Start at the beginning of the series – and all the source can be found in my github repo) Compile-time random number generation is quite useful. For instance, we could generate GUIDs (version 4 UUIDs): namespace cx { struct guid_t { uint32_t data1; uint16_t data2; uint16_t data3; uint64_t data4; };…

Read More

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

©2026 Why is a raven like a writing desk? | WordPress Theme by SuperbThemes