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
xis already printable, that is,operator<<might work "out of the box". - If
xis a container, we want to do something with that and print the elements. - We probably want to print elements of
pairandtuplealso. - 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:
- There are at least 6 types of callable things: functions, member functions,
std::functions, bind expressions, lambdas, and objects that supportoperator()(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. nullptris its own type and unprintable.enum classvalues are not printable out-of-the-box; they will require a cast tostd::underlying_type.pairs should be easy to deal with, buttuples 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<<.