In the first installment of "Category theory for programmers", Bartosz Milewski introduces a particular category in which objects and morphisms are thought as C++ values and C++ functions, respectively.

Instead, let us try to delineate the category in which objects are C++ types. In such a category, morphisms are metafunctions rather than ordinary functions. Thus, the identity morphisms are all representable via the following id metafunction:

template <typename T>  
using id = T;  

We can introduce other morphisms in a similar fashion. For instance, we can come up a morphism that maps from some type T to T*:

template <typename T>  
using add_pointer = T*;  

Morphisms can be composed through a correspoding compose operator, implemented as:

template <  
  template <typename> class f, 
  template <typename> class g
>
struct compose {  
  template <typename T>
  using apply = g<f<T>>;
};

With all the elements in place, we are now in the condition to put our category at work. As an example, we can check the identity law:

// Identity laws
using int_ptrL = compose<id, add_pointer>::apply<int>;  
using int_ptrR = compose<add_pointer, id>::apply<int>;

static_assert(std::is_same_v<int_ptrL, int_ptrR>);