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

I wrote my first Python program

20 January, 200920 January, 2009

Last Friday. Knowing almost no Python at noon, by 5pm I had some code to munge XML and do something useful for my current project. So it’s not bad. It’s good for productivity. Mostly because: It has useful libraries. Bread-and-butter data structures are built in, i.e. lists and dictionaries. I…

Read More

The Camel Has Two Humps

6 June, 200729 July, 2007

I’ve just been reading an interesting paper on teaching programming. First, the data regarding rates of attrition on computing courses is startling. Between 30% and 60% of the total CS intake fails the very first programming course! The paper’s authors postulate 3 programming hurdles to be negotiated: sequencing and assignment…

Read More

Rules for using <random>

8 April, 20153 June, 2025

These days, it’s easy to do the right thing. Don’t do this: Don’t use std::rand(). Ever. Don’t use std::random_shuffle() to permute containers. (Too easy to misuse; can use std::rand() under the hood.) Don’t use any kind of clock for a seed. Don’t use mod (%) to get a random value…

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