Как работает int *** в этой метапрограмме шаблона?
Как работает метапрограммирование шаблонов (static const int value = 1 + StarCounter<\U>::value;
) для печати 3
?
#include <iostream>
template <typename T>
struct StarCounter
{
static const int value = 0;
};
template <typename U>
struct StarCounter<U*>
{
static const int value = 1 + StarCounter<U>::value;
};
int main()
{
std::cout << StarCounter<int***>::value << std::endl;//How is it printing 3?
return 0;
}
Ответы
Ответ 1
Первый шаблон создает структуру, которая всегда будет возвращать 0 при вызове StarCounter<U>::value
.
Второй шаблон специализируется на первом случае для случаев, когда используется указатель. Поэтому, когда вы вызываете его с помощью StarCounter<U*>::value
, используется второй шаблон, а не первый, и он возвращает StarCounter<U>::value + 1
. Обратите внимание, что он удаляет указатель на каждом этапе рекурсии.
Таким образом, вызов StarCounter<int***>::value
будет затрачен на:
StarCounter<int***>::value // second template is used due to pointer
1 + StarCounter<int**>::value // second template is used due to pointer
1 + 1 + StarCounter<int*>::value // second template is used due to pointer
1 + 1 + 1 + StarCounter<int>::value // no pointer here, so first template is used
1 + 1 + 1 + 0
3
Ответ 2
StarCounter<int>::value
равно 0
, поскольку он соответствует первому экземпляру шаблона, где value
явно определено.
StarCounter<int*>::value = 1 + StarCounter<int>::value
равно 1
, потому что StarCounter<int*>
соответствует StarCounter<U*>
. Да, StarCounter<T>
также можно рассматривать как совпадение, но StarCounter<U*>
является более конкретным и почему этот вариант является предпочтительным.
Аналогично,
StarCounter<int**>::value = 1 + StarCounter<int*>::value
равно 2
и
StarCounter<int***>::value = 1 + StarCounter<int**>::value
равно 3
.
Ответ 3
Я нахожу, что это помогает думать о эквивалентах времени исполнения, когда дело доходит до метапрограммирования. В метапрограммировании шаблонов мы используем частичную специализацию, как при программировании во время выполнения, мы используем рекурсию. Основной шаблон функционирует как базовый случай, а функция специализаций - как рекурсивные случаи.
Рассмотрим следующую рекурсивную версию определения размера контейнера:
def size(x):
if empty(x):
return 0
else:
return 1 + size(tail(x))
Это эквивалент кода шаблона, который вы представляете. Основной шаблон, StarCounter<T>
, является базовым. Пустой случай. Он имеет размер (value
) ноль. Специализация StarCounter<U*>
- это рекурсивный случай. Он имеет размер (value
) 1
плюс размер рекурсии с хвостом (StarCounter<U>
).
В С++ 17 мы можем еще более явно сделать версию метапрограммирования эквивалентной рекурсивной версии runtime (это представлено исключительно как пример, а не как способ написания этого кода):
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();
};
Ответ 4
Существует шаблон StarCounter
, который в нем более общий вид имеет константу value
, равную 0
. Поэтому, когда вы используете этот шаблон для большинства типов и запрашиваете value
, вы получите 0.
Этот шаблон также имеет специализированную версию, которая принимает указатели.
Реализация также имеет константу value
, которая равна 1 (поскольку у нас есть указатель, который означает, что у нас есть хотя бы одна звезда) плюс значение value
типа, на который указывает этот указатель.
В случае трех звезд имеем:
StarCounter<int***>::value = 1 + StarCounter<int**>::value (1) + StarCounter<int*>::value (1) + StarCounter<int>::value (0)