Причина использования параметра типа non-type вместо обычного параметра?
В С++ вы можете создавать шаблоны с использованием параметра шаблона, отличного от типа:
template< int I >
void add( int& value )
{
value += I;
}
int main( int argc, char** argv )
{
int i = 10;
add< 5 >( i );
std::cout << i << std::endl;
}
Что печатает "15" в cout. Для чего это нужно? Есть ли какая-либо причина использования параметра шаблона непигового типа вместо более обычного, например:
void add( int& value, int amount )
{
value += amount;
}
Извините, если это уже было задано (я посмотрел, но ничего не нашел).
Ответы
Ответ 1
Существует множество приложений для аргументов шаблона, отличных от типа; вот несколько:
Вы можете использовать аргументы non-type для реализации генерических типов, представляющих массивы или матрицы фиксированного размера. Например, вы можете параметризовать тип Matrix
по его размерам, чтобы вы могли сделать Matrix<4, 3>
или Matrix<2, 2>
. Если вы затем правильно определите перегруженные операторы для этих типов, вы можете предотвратить случайные ошибки при добавлении или умножении матриц неправильных размеров и может выполнять функции, которые явно сообщают ожидаемые размеры матриц, которые они принимают. Это предотвращает появление огромного класса ошибок времени выполнения, обнаруживая нарушения во время компиляции.
Вы можете использовать аргументы non-type для реализации оценки функции компиляции с помощью метапрограммирования шаблонов. Например, здесь простой шаблон, который вычисляет факториал во время компиляции:
template <unsigned n> struct Factorial {
enum {
result = n * Factorial<n - 1>::result
};
};
template <> struct Factorial<0> {
enum {
result = 1
};
};
Это позволяет вам писать код типа Factorial<10>::result
, чтобы получить во время компиляции значение 10!. Это может предотвратить выполнение дополнительного кода во время выполнения.
Кроме того, вы можете использовать аргументы non-type для реализации анализа параметров времени компиляции, который позволяет вам определять типы для килограммов, метров, секунд и т.д. что компилятор может гарантировать, что вы случайно не используете килограммы, где вы имели в виду счетчики и т.д.
Надеюсь, это поможет!
Ответ 2
Вероятно, вы правы в этом случае, но есть случаи, когда вам нужно знать эту информацию во время компиляции:
Но как насчет этого?
template <std::size_t N>
std::array<int, N> get_array() { ... }
std::array
должен знать свой размер во время компиляции (поскольку он выделен в стеке).
Вы не можете сделать что-то вроде этого:
std::array<int>(5);
Ответ 3
В этом конкретном случае на самом деле нет никакого преимущества. Но используя такие параметры шаблона, вы можете сделать много чего, чего не могли бы сделать иначе, например эффективно связать переменные с функциями (например, boost::bind
), указать размер массива времени компиляции в функции или классе (std::array
является готовым примером этого) и т.д.
Например, с помощью этой функции вы пишете функцию типа
template<typename T>
void apply(T f) {
f(somenum);
}
Затем вы можете передать apply
функцию:
apply(&add<23>);
Это очень простой пример, но он демонстрирует принцип. Более продвинутые приложения включают применение функций к каждому значению в коллекции, вычисление таких вещей, как факториал функции во время компиляции, и многое другое.
Вы не могли бы сделать это иначе.
Ответ 4
Хорошо, это типичный выбор между полиморфизмом времени компиляции и полиморфизмом во время выполнения.
Из формулировки вашего вопроса видно, что вы не видите ничего необычного в "обычных" параметрах шаблона, хотя воспринимаете непиковые параметры как нечто странное и/или избыточное. На самом деле та же проблема может быть применена к параметрам типа шаблона (так называемые "обычные" параметры). Идентичные функции часто могут быть реализованы либо через полиморфные классы с виртуальными функциями (полиморфизм во время выполнения), либо через параметры типа шаблона (полиморфизм времени компиляции). Можно также спросить, почему нам нужны параметры типа шаблона, поскольку практически все может быть реализовано с использованием полиморфных классов.
В случае нетиповых параметров вы можете иметь что-то подобное в этот день
template <int N> void foo(char (&array)[N]) {
...
}
который не может быть реализован со значением времени выполнения.
Ответ 5
Есть много причин, например, делать метапрограммирование шаблонов (проверьте Boost.MPL). Но нет необходимости заходить так далеко, С++ 11 std::tuple
имеет аксессор std::get<i>
, который нужно индексировать во время компиляции, так как результат зависит от индекса.
Ответ 6
Наиболее частое использование параметра значения, о котором я могу думать, это std::get<N>
, который извлекает N-й элемент из std::tuple<Args...>
. Второе наиболее частое использование было бы std::integral_constant
и его основных производных std::true_type
и std::false_type
, которые являются повсеместными в любых классах признаков. На самом деле, черты типа полностью переполнены параметрами шаблона значений. В частности, существуют методы SFINAE, которые используют шаблон подписи <typename T, T>
для проверки существования члена класса.