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 2)

elbeno, 1 February, 201530 June, 2015

Part 1 Part 2 Part 3 Part 4 Part 5 Postscript

We have a basic plan, and the ability to detect concrete types. But how can we detect whether an object supports output with operator<<? For this, there is a recently-discovered amazing trick. Here’s the code:

template 
using void_t = void;

template 
using operator_output_t = decltype(std::cout << std::declval());

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

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

The key here is void_t. It’s a template alias that takes any template arguments, and always produces void. So what’s the use of that? Well, it doesn’t take just any old arguments. It can only work with well-formed types. So what happens if we give it a type that isn’t well-formed? Nothing too bad – because of course, Substitution Failure Is Not An Error.

Look at operator_output_t. It uses decltype to produce the type of outputting a T. If T doesn’t support operator<<, this type will be invalid.

Now look at has_operator_output. We specialize it using the result of void_t on operator_output_t<T>. If this is ill-formed (T can’t support operator<<) then substitution fails and the unspecialized template, std::false_type, is used. But if T supports operator<<, the result of void_t is well-formed. It’s still void, but it’s more specialized than the default template and so we get std::true_type!

That is the magic that void_t allows; a whole new frontier of template metaprogramming. (And if we relax the “because-we-can-be-general” variadic template, and convert the template aliases, it’s plain old C++98 specialization! Although it’s the combo with decltype and declval that really gives it phenomenal cosmic power.)

I’m going to set up a template to work on the tag types, so that the default implementation of stringifier in part 1 now becomes stringifier_select with a tag type that we can specialize on:

template 
struct stringifier_select;

template 
using stringifier = stringifier_select>;

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

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

And I can set up type discrimination for outputtable types, and implement their output function trivially:

template 
using stringifier_tag = std::conditional_t<
  has_operator_output::value,
  is_outputtable_tag,
  void>;

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

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

  const T& m_t;
};

So, now that we know the void_t pattern, it's trivial to use it for detecting any container type; in fact, any type that has begin() and end().

template 
using begin_t = decltype(std::declval().begin());

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

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

template 
using end_t = decltype(std::declval().end());

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

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

And then we have a very straightforward way to detect a container or other iterable type:

template
using is_iterable = typename std::conditional<
  has_begin::value && has_end::value,
  std::true_type, std::false_type>::type;

To actually output a container, we will need to output surrounding braces and a comma in between each element. We might want to vary opening/closing/separating characters by container type, so I want to allow them to be specialized:

template 
struct iterable_opener
{
  constexpr const char* operator()(const T&) const
  { return "{"; }
};

template 
struct iterable_closer
{
  constexpr const char* operator()(const T&) const
  { return "}"; }
};

template 
struct iterable_separator
{
  constexpr const char* operator()(const T&) const
  { return ","; }
};

I originally implemented each of these as a straightforward const char*, but using functions and passing the container itself allows them to access (for example) size information which might be useful.

Now that we have the customization points in place, we can write the specialization of stringifier for containers. There's a minor wrinkle to deal with fenceposting the separators.

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

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

(Notice that it calls prettyprint to output each element recursively.) And that's it, we can handle all iterable things. Next, handling pair and tuple.

C++ Programming

Post navigation

Previous post
Next post

Related Posts

constexpr Function Parameters

29 August, 202229 August, 2022

The Set-up In C++, this doesn’t work: The compiler complains, quite rightly: Despite the fact that twice_square is a constexpr function, its parameter x is not constexpr. We can’t use it in a static_assert, we can’t pass it to a template, we can’t call an immediate (consteval) function with it….

Read More

Profiling with SBCL

29 February, 200829 February, 2008

My Bézier curve code was not optimal, so I decided to learn how to profile with SBCL. In particular, I was not yet doing a binary search of the sampled points, neither was I doing an interpolation to recover the parameter t. Finally, I was sampling the curve at a…

Read More

Exercising Ranges (part 6)

1 July, 20151 July, 2015

(Start at the beginning of the series if you want more context.) Multiplying power series Now we come to something that took considerable thought: how to multiply ranges. Doug McIlroy’s Haskell version of range multiplication is succinct, lazily evaluated and recursive. (f:ft) * gs@(g:gt) = f*g : ft*gs + series(f)*gt…

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