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

Part 1 Part 2 Part 3 Part 4 Part 5 Postscript

I thought I’d have a go at writing some code that could print things. A pretty-printer, if you like. What I want to be able to do is this:

 // Print x correctly, where x is ANY type.
cout << prettyprint(x) << endl;

Obviously this is going to involve templates since the type of x may vary.

There are a few things that immediately spring to mind:

  • Maybe x is already printable, that is, operator<< might work "out of the box".
  • If x is a container, we want to do something with that and print the elements.
  • We probably want to print elements of pair and tuple also.
  • The value of some things is not printable, or at least not sensible to print. For example, functions.

The basic plan that came to mind is this: prettyprint is a function template, which returns an object that wraps the x and has operator<< defined. This class will itself be a template, and the template instantiation we choose will be determined by what sort of thing x is.

This suggest that the very first thing we can write looks something like this:

template 
struct stringifier
{
  explicit stringifier(const T&) {}

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

template 
inline std::ostream& operator<<(std::ostream& s, 
                                const stringifier& t)
{
  return t.output(s);
}

template 
inline stringifier prettyprint(T&& t)
{
  return stringifier(std::forward(t));
}

So far so good. Now we can print "<unknown>" for any type. We have a place to start specializing things.

A quick poke around C++ type support and some experiments soon fleshed out the plan:

  1. There are at least 6 types of callable things: functions, member functions, std::functions, bind expressions, lambdas, and objects that support operator() (function objects). All of these are "unprintable". Lambdas are a kind of function object; I don't know whether they can be meaningfully distinguished or whether that is desirable.
  2. nullptr is its own type and unprintable.
  3. enum class values are not printable out-of-the-box; they will require a cast to std::underlying_type.
  4. pairs should be easy to deal with, but tuples will require some effort. They are designed for static access, not iteration.

So now I have 4 broad categories I want to represent for printing: already-outputtable, unprintable, callable, and containers/pairs/tuples. I decided to make tag types for each specializable thing so as to be able to select on them.

struct is_iterable_tag {};
struct is_pair_tag {};
struct is_tuple_tag {};
struct is_callable_tag {};
struct is_outputtable_tag {};
struct is_enum_tag {};
struct is_unprintable_tag {};

As luck would have it, several of these already have built-in support for detecting:

template
using is_unprintable = typename std::conditional<
  std::is_union::value ||
  std::is_class::value ||
  std::is_null_pointer::value,
  std::true_type, std::false_type>::type;

// similarly we can use: 
// std::is_enum::value to detect enum or enum class

Detecting pairs and tuples is also easy, using a standard specialization pattern:

template 
struct is_pair : public std::false_type {};

template 
struct is_pair> : public std::true_type {};

template 
struct is_tuple : public std::false_type {};

template 
struct is_tuple> : public std::true_type {};

A good setup so far. We can detect concrete types. Next, how to detect whether something already supports operator<<.

Leave a comment

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.