is_printable
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.