Fundamentals
Function on Types
We all know that C++ templates can receive types as “parameters”. In the same spirit we could can “return values”s, so that we can form a function. The basic paradigm is the following:
template<class T>
struct Computed {
using type = T;
}
In this way we constructed a type level function named Computed, with one parameter and result the parameter itself. We use the function as Computed<float>::type, the result being float itself.
Why an extra structwrapper instead of directly defining the “function”? That’s because partialization on using is not allowed. Such codes are invalid:
template <class T> using computed<T> = T; template<> using computed<int> = double;
Partialization
template<class T>
struct Computed {
using type = T;
}
template<>
struct Computed<int> {
using type = double;
}
Computed<bool>::type would become bool, while Computed<int>::type would (specially) be double. Certainly, there are a set of rules for the matching process. E.g. the most “specific” case gets matched first. This is somewhat from Haskell, where the first-defined case comes first. The equivalent code in Haskell is:data Type = Bool | Double | Int computed :: Type -> Type computed Int = Double computed t = t
Peano numbers
Zero for 0, and a Succ for the successive. Thus 1 would be Succ<Zero>, 2 would be Succ<Succ<Zero>>, and so on. This is but the heuristic demonstration of “induction”.struct Peano {};
struct Zero: Peano {};
template<class T>
struct Succ: Peano {};
template<class T1, class T2>
struct Add {
using type = ???;
}
2+1=3, or Add<Succ<Succ<Zero>>, Succ<Zero>>::type = Succ<Succ<Succ<Zero>>>. Certainly the latter expression is not valid C++, since there is no equality operator for types. Instead, we should use the std::is_same<T1, T2> function, which is predefined in <type_traits>. Or you can implement it yourself, again using partialization. This is a possible implementation:template<class T, class U>
struct is_same {
static constexpr bool value = false;
};
template<class T>
struct is_same<T, T> {
static constexpr bool value = true;
};
template<>
struct Add<Succ<Succ<Zero>>, Succ<Zero>> {
using type = Succ<Succ<Succ<Zero>>>;
}
0 + b = b, (Succ a) + b = Succ (a + b). In C++ they are:template<class T1, class T2>
struct Add;
template<class T>
struct Add<Zero, T> {
using type = T;
};
template<class T1, class T2>
struct Add<Succ<T1>, T2> {
using type = Succ<typename Add<T1, T2>::type>
};
typename — the compiler doesn’t know whether the ::type is a member type or a member variable, so we need this keyword as a hint.std::is_same<Add<Succ<Succ<Zero>>, Succ<Zero>>::type, Succ<Succ<Succ<Zero>>>>::value == true or so. Such nesting of Succ is somewhat hard to read, so you can simply write a template to generate Peano types from integers:template<int v>
struct peano {
using type = Succ<typename peano<v - 1>::type>;
};
template<>
struct peano<0> {
using type = Zero;
};
Then you can simplify it to Add<peano<2>::type, peano<1>::type>::type and peano<3>::type. The other opeartions like subtraction, multiplication and division can be similarly implemented, which are left as exercises.
Exercises:
Afterwords
We can further “prove” the assertions in our intuition, such addition is commutative, multiplication is distributive over addition, and so on. We may talk about this later.