Подтвердить что ты не робот

Вычисление факториала с использованием мета-программирования шаблонов

Я не понимаю, как этот фрагмент кода (из Википедии) работает:

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}
  • Что это за странный шаблон, который принимает <int N>?
  • Что это за второй странный шаблон <>?
  • Что такое enum для?
  • В чем преимущество использования этого, а не нормального факториальный расчет времени выполнения?
  • Как часто вы это используете? Я уже некоторое время использую С++, но никогда раньше этого не использовал. Насколько большой частью С++ я отсутствовал?

Спасибо!

4b9b3361

Ответ 1

  • Что это за странный шаблон, который принимает <int N>?

В С++ аргументы шаблона могут быть либо типами (с префиксом class или typename), либо целыми числами (с префиксом int или unsigned int). Здесь мы находимся во втором случае.

  • Что это за второй странный template <>?

template<> struct Factorial<0> - полная специализация шаблона Factorial, что означает, что 0 считается специальным значением, которое соответствует его собственной версии Factorial.

  • Что такое перечисления для?

перечисления - это способ вычисления значений в метапрограммировании С++

  • В чем преимущество использования этого, а не обычного факториального расчета времени выполнения?

Причина, по которой этот код был создан в первую очередь, заключается в том, чтобы создать доказательство концепции, что исчисление может быть выполнено с помощью метапрограммирования. Преимущество состоит в том, что сгенерированный код чрезвычайно эффективен (вызов Factorial<4>::value эквивалентен простому написанию "24" в вашем коде.

  • Как часто вы это используете? Я уже некоторое время использую С++, но никогда раньше этого не использовал. Насколько большой частью С++ я отсутствовал?

Такая функциональность редко достигается с помощью этого метода, но метапрограммирование используется все больше и больше в наши дни. См. Увеличьте библиотеку метапрограмм, чтобы получить подсказку о том, что можно сделать.

Ответ 2

Что это за странный шаблон, который принимает <int N>?

Объявление шаблона может быть сделано для классов/типов, значений и указателей. Обычно вы не видите эту форму, поскольку определяющие шаблоны редко используют константы в параметрах шаблона.

Что это за второй странный шаблон < > ?

Лучший способ подумать о шаблоне - это сделать что-то похожее на то, что делает компилятор: рассмотрите шаблон как класс, где вы заменяете параметр шаблона фактическим значением этого типа.

То есть для:

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

Factorial<2>::value эквивалентно:

// generated by the compiler when Factorial<2>::value is encountered in code:
struct Factorial2 { enum { value = 2 * Factorial1::value }; };
struct Factorial1 { enum { value = 1 * Factorial0::value }; };
// not generated by compiler, as it was explicitly defined in the code you ask about:
template <> struct Factorial<0> { enum { value = 1 }; }; // defines Factorial<0>

Что такое перечисления для?

Они определяют перечисление с константой value во время компиляции, позволяя вам писать код клиента, подобный тому, который указан в вашем примере.

В чем преимущество использования этого а не нормальное время выполнения факториала расчет?

Преимущество - это эффективность. Поскольку вычисление значения расширяется при компиляции, стоимость времени выполнения Factorial<10>::value совпадает с значением Factorial < 1 > ::. В скомпилированном коде они являются константами. Если вы должны были рассчитать факторный код, зависящий от производительности, и вы знали, во время компиляции это значение, вы должны либо сделать это, либо рассчитать его в автономном режиме, либо определить константу с предварительно рассчитанным значением в вашем коде.

Как часто вы это используете?

Является ли "this" ссылкой на рекурсивное вычисление константы? Если да, то не часто.

Является ли "this" определение констант в шаблонах? Очень часто:)

Является ли "this" спецификацией шаблона для определенного типа? Очень Очень Очень часто. Я понял, что std::vector имеет полностью отдельную реализацию в некоторых реализациях STL (a std::vector<bool> с 8 элементами не требуется больше 1 байта для хранения значений).

Я использую С++ некоторое время, но никогда не использовал это раньше. Насколько велика часть С++ была упущена?

Не обязательно большая часть. Если вы используете boost, вероятно, что используемый вами код реализуется с помощью таких вещей.

Ответ 3

Я нашел этот ответ Johannes Schaub - litb по другому вопросу очень полезный.

[Я копирую здесь ответ, предполагая, что Йоханнес в порядке. Я удалю пасту, если ему это не нравится]


Да, это параметр не-типа. Вы можете иметь несколько типов параметров шаблона

  • Введите параметры.
    • Типы
    • Шаблоны (только классы, без функций)
  • Неигровые параметры
    • Указатели
    • Ссылки
    • Интегральные константные выражения

У вас есть последний вид. Это константа времени компиляции (так называемое постоянное выражение) и имеет тип integer или перечисление. После поиска в стандарте мне пришлось переместить классные шаблоны в раздел типов - хотя шаблоны не являются типами. Но они называются параметрами типа с целью описания тех видов. У вас могут быть указатели (а также указатели элементов) и ссылки на объекты/функции, которые имеют внешнюю привязку (те, которые могут быть связаны с другими объектными файлами и адрес которых уникален во всей программе). Примеры:

Параметр типа шаблона:

template<typename T>
struct Container {
    T t;
};

// pass type "long" as argument.
Container<long> test;

Параметр целочисленного шаблона:

template<unsigned int S>
struct Vector {
    unsigned char bytes[S];
};

// pass 3 as argument.
Vector<3> test;

Параметр указателя шаблона (передача указателя на функцию)

template<void (*F)()>
struct FunctionWrapper {
    static void call_it() { F(); }
};

// pass address of function do_it as argument.
void do_it() { }
FunctionWrapper<&do_it> test;

Параметр ссылки шаблона (передача целого числа)

template<int &A>
struct SillyExample {
    static void do_it() { A = 10; }
};

// pass flag as argument
int flag;
SillyExample<flag> test;

Параметр шаблона шаблона.

template<template<typename T> class AllocatePolicy>
struct Pool {
    void allocate(size_t n) {
        int *p = AllocatePolicy<int>::allocate(n);
    }
};

// pass the template "allocator" as argument. 
template<typename T>
struct allocator { static T * allocate(size_t n) { return 0; } };
Pool<allocator> test;

Шаблон без каких-либо параметров невозможен. Но шаблон без явного аргумента возможен - он имеет аргументы по умолчанию:

template<unsigned int SIZE = 3>
struct Vector {
    unsigned char buffer[SIZE];
};

Vector<> test;

Синтаксически template<> зарезервировано для обозначения явной специализации шаблона вместо шаблона без параметров:

template<>
struct Vector<3> {
    // alternative definition for SIZE == 3
};

Ответ 4

В чем преимущество использования этого, а не обычного факториального расчета времени выполнения?

Это быстрее - теоретически компилятор будет расширять набор шаблонов во время компиляции, так что Factorial<n>::value - это просто одна константа. В некоторых исчезающе малых количествах это может иметь значение.

Нижняя сторона этого заключается в том, что его нужно вычислять во время компиляции, поэтому, если ваш n определяется во время выполнения, вы не можете использовать этот метод.

Как часто вы это используете? Я использую С++ некоторое время, но никогда не использовал это раньше. Насколько велика часть С++, на которой я отсутствовал?

Случай вроде этого, не часто. Метапрограммирование шаблонов потенциально очень мощно, но количество случаев, когда этот пример полезен и достаточно важен для производительности, крошечный.

Но это полезный метод в С++, поскольку он позволяет языку делать много мощных трюков, которые иначе были бы недоступны. Я бы рискнул, что гораздо чаще использовать метапрограммирование шаблонов, которые кто-то сделал для вас - например. Boost использует его довольно сильно и в результате может сделать очень умные вещи. Он мощный, но может также обфускать код, если не сделано хорошо.

Ответ 5

Рекурсия, выполняемая компилятором, где

template <>
struct Factorial<0>
{
}

- точка выхода рекурсии.

Сначала Factorial<4> разрешается. Специализации Factorial для значения 4 нет, поэтому используется первое определение Factorial<>. Затем это значение вычисляется с помощью Factorial<3>::value, где повторяется предыдущий шаг.

Это будет продолжаться до N == 1, затем для N-1 вступает в действие специализация Factorial<0>, где значение равно 1. Рекурсия останавливается здесь, и компилятор может идти вверх и вычисляет оставшиеся значения Factorial<N>.