Update: This post really took off on /r/cpp and was trending! You can have a look at the comments here.

While playing around with C++ templates and SFINAE, I came up with the idea of a new metafunction named is_printable which tells you, at compile-time, if a value of a particular type can be printed using std::cout. So something like is_printable<int>::value will return true since int is a fundamental type. But something like is_printable<std::vector<int>>::value will return false.

Motivation

The closest helper function I could find in the standard library was isprint. However, this function only checks if a single character (from a pre-defined set of values) is printable. It also does this at runtime. But I want to be able to check if any type can be printed and I want to do that at compile-time. Why? Because it is possible to do so!

First attempt

I wanted is_printable to have a look and feel like the other standard library metafunctions. This meant that it will be a struct that contains a boolean static member named value. Since I am going to be using C++17, I also wanted to have is_printable_v<TYPE> as a shortform to is_printable<TYPE>::value.

Let us lay down our basic structure.

template<typename _Tp>
struct is_printable {
    static constexpr bool value = false;
};

template<typename _Tp>
inline constexpr bool is_printable_v = is_printable<_Tp>::value;

At this stage, if someone was to call it with any type, it is, obviously, going to return false.

How about, for simplicity, we start by only checking if we want to print a fundamental type. Thankfully, we have such a method in the standard library named is_fundamental. We can now change is_printable accordingly.

template<typename _Tp>
struct is_printable {
    static constexpr bool value = std::is_fundamental_v<_Tp>;
};

Now, something like is_printable_v<int> is going to return true but is_printable_v<std::vector<int>> is going to return false. And rightly so because we cannot just print a std::vector like that.

But we can implement a specialised operator<<() for such a type to become printable. Aha!

SFINAE to the rescue

Ideally, we want to check for all values that can be printed using std::cout. This is where we will be leveraging the power of SFINAE. To accomplish this, we can just try to print the respective type with std::cout at compile-time and check for its validity. We will be using the following for our implementation.

  • decltype can deduce the type of an entity or that of an expression, at compile-time.
  • std::declval can be used to convert any type T to a reference type, making it possible to use member functions in decltype expressions without the need to go through constructors.
  • std::enable_if is a metafunction that is useful to leverage SFINAE to conditionally remove functions from overload resolution based on type traits and to provide separate function overloads and specializations for different type traits.
  • std::is_same checks for the equality of two types.

Using the above mentioned helpers, our code will look something like this.

template<typename _Tp, typename dummy = void>
struct is_printable : std::false_type {};

template<typename _Tp>
struct is_printable<_Tp, 
        typename std::enable_if_t<
            std::is_same_v<decltype(std::cout << std::declval<_Tp>()), std::ostream&>
        >
    > : std::true_type {};

There we have it. Our brand new is_printable metafunction that deduces if a type is printable or not at compile-time. The example usage will be like this

is_printable_v<int>
is_printable_v<unsigned char>
is_printable_v<std::vector<int>>
is_printable_v<C> // where C is a custom type

I had a lot of fun while working on this and I hope you learnt something new from this post.

The final code with an example is available at https://github.com/mnafees/cpp_utils/tree/master/is_printable.