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

Thoughts on Modern C++ and Game Dev

1 January, 20191 January, 2019

TL;DR: The C++ committee isn’t following some sort of agenda to ignore the needs of game programmers, and “modern” C++ isn’t going to become undebuggable. — Over the past week there has been an ongoing conversation on Twitter about how many people — especially those in the games industry —…

Read More

Compile-time counters, revisited

14 October, 201515 October, 2015

(Start at the beginning of the series – and all the source can be found in my github repo) Some time ago I read a blog post showing how to make a compile-time counter: a constexpr function that would return monotonically increasing integers. When I first read it I didn’t…

Read More

BSOL

30 March, 200629 July, 2007

I am currently doing work on PS3, and our renderer by default colours the screen blue. So when the game starts up without crashing, before any data is loaded and displayed, we at least get what I like to refer to as the “Blue Screen of Life”.

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