I find it helps to think of runtime equivalents when it comes to metaprogramming. In template metaprogramming, we use partial specialization, as in runtime programming, we use recursion. The primary template functions as the base case and the specializations function as the recursive cases.
Consider the following recursive version of determining the size of a container:
def size(x):
if empty(x):
return 0
else:
return 1 + size(tail(x))
This is the equivalent of the template code you present. The primary template, StarCounter<T>
, is the base case. The empty case. It has size (value
) zero. The specialization, StarCounter<U*>
, is the recursive case. It has size (value
) 1
plus the size of recursing with the tail (StarCounter<U>
).
In C++17, we can even more explicitly make the metaprogramming version equivalent to the runtime recursive version (this is presented solely as an example and not as a way that this code should be written):
template <class T>
struct StarCounter {
static constexpr int calc_value() {
if constexpr (!std::is_pointer<T>::value) {
return 0;
}
else {
return 1 + StarCounter<std::remove_pointer_t<T>>::value;
}
}
static constexpr int value = calc_value();
};
StarCounter<int***>
is the same thing asStarCounter<U*>
whereU
isint**
which then usesStarCounter<int**>
and so on ... – Lavonna\
insideStarCounter<\U>::value
? (I can't do single character edits) – Cookbook