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

elbeno, 2 February, 201530 June, 2015

Part 1 Part 2 Part 3 Part 4 Part 5 Postscript

Callable things. There are several types:

  • functions
  • member functions
  • std::functions
  • bind expressions
  • lambdas
  • objects that support operator() (function objects)

So, going back to my tag code, so far (with everything I’ve added) and including callable things, it will look like:

template 
using stringifier_tag = std::conditional_t<
  is_outputtable::value,
  is_outputtable_tag,
  std::conditional_t<
    is_callable::value,
    is_callable_tag,
    std::conditional_t<
      is_iterable::value,
      is_iterable_tag,
      std::conditional_t<
        is_pair::value,
        is_pair_tag,
        std::conditional_t<
          is_tuple::value,
          is_tuple_tag,
          void>>>>>;

This is getting to be a large nested “if-statement”, but it’s easy to follow, and the compiler doesn’t mind, so I don’t. Basically, this is the preferential order I want to use for outputting things. But then I discovered something puzzling, that took me a while to figure out. (In my defence, I discovered it late at night when I was probably not too sharp!)

// this outputs "1"!
cout << [](){} << endl;

Lambdas (and in fact, functions) can be passed to operator<< - which means they'll get is_outputtable_tag and produce 1 when printed. Not good. I want to print "<callable (function)>" or "<callable (function object)>". (I'm OK with lambdas and function objects coinciding here.) So why does printing a plain lambda work at all? Well, the answer (which took me too long to see, and doubtless you, learned reader, have already seen) is that a non-capturing lambda has an implicit conversion to a function pointer. And that, like all pointers, has an implicit conversion to bool. Anything non-zero (like a perfectly good pointer) is true, and when you print true (without using boolalpha), you get 1.

Hm. Think think think.

So, I think there is going to be a compromise here. And that compromise is going to be triggered if someone deliberately writes a function object with a conversion to bool and supporting operator<<. Because lambdas have operator() and a conversion to bool, and I don't want to use operator<< on them. So, it's void_t to the rescue again, and by now I have macroed the detection code.

#define SFINAE_DETECT(name, expr)                \
  template                           \
  using name##_t = decltype(expr);               \
  template          \
  struct has_##name : public std::false_type {}; \
  template                           \
  struct has_##name>>      \
    : public std::true_type {};

I want to know if something's implicitly convertible to bool:

void bool_conversion_test(bool);
SFINAE_DETECT(bool_conversion, 
              bool_conversion_test(std::declval()))

I don't need to implement anything for bool_conversion_test because it's never called, just used to check well-formedness of conversion. I also want to know if something has a function call operator:

SFINAE_DETECT(call_operator, &T::operator())

And now I can massage is_outputtable appropriately: something that has operator<< but is not a function and is not a function object convertible to bool:

template
using is_outputtable = typename std::conditional<
  has_operator_output::value &&
  !std::is_function::value &&
  !(has_call_operator::value && has_bool_conversion::value),
  std::true_type, std::false_type>::type;

Now that I've eliminated the callable types from the outputtable types, the detection of a callable type is fairly straightforward, between what the STL gives me and what is easy to make myself:

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

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

template
using is_callable = typename std::conditional<
  has_call_operator::value
  || is_std_function::value
  || std::is_function::value
  || std::is_bind_expression::value,
  std::true_type, std::false_type>::type;

And now all that's left is to distinguish between the callable types themselves for the purposes of printing. This can be done with judicious use of std::enable_if.

template 
constexpr static std::enable_if_t::value, 
                                  const char*>
callable_type() { return "(std::function)"; }

template 
constexpr static std::enable_if_t::value, 
                                  const char*>
callable_type() { return "(function)"; }

template 
constexpr static
std::enable_if_t::value,
                 const char*>
callable_type() { return "(bind expression)"; }

template 
constexpr static
std::enable_if_t::value && 
                  has_call_operator::value,
                 const char*>
callable_type() { return "(function object)"; }

Finally, I am ready to write the actual printing code for callable types.

template 
struct stringifier_select
{
  explicit stringifier_select(T) {}

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

Phew!

Recap: so far, we can print:

  • things that have operator<<
  • containers
  • pair and tuple
  • callable things
  • and by default, things that just say "unknown"

Next, what to do about strings and arrays?

C++ Programming

Post navigation

Previous post
Next post

Related Posts

“In-place” might be less in-place than you think

5 May, 2015

The intuitive view of algorithms that work in-place is that (it sounds obvious) they don’t use any extra space. Canonically in C/C++ we think of something like reversing an array, or that interview staple, removing spaces from an ASCII string, which we might write as: int remove_spaces(char *s) { char…

Read More

Why programming is fun

8 July, 200629 July, 2007

This week I have been working with a colleague on optimising a piece of code. Last week, what we started out with (a first implementation) was taking over 80ms to run in my test case. We had confidence that the overall algorithm was the correct one for the job, so…

Read More

Why Reading Books Matters to a Programmer

29 April, 200829 April, 2008

The programming book market these days is small. Nothing like what it was eight years ago or so. And apparently, programmers don’t read books (any more). It’s mostly true. But of course, there are still books worth reading. I’m going to take as read the easy arguments: let’s assume that…

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