Причина использования нетипичного параметра шаблона вместо обычного параметра? - PullRequest
24 голосов
/ 13 сентября 2011

В C ++ вы можете создавать шаблоны, используя нестандартный параметр шаблона, например:

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;
}

Извините, если об этом уже спрашивали (я посмотрел, но ничего не нашел).

Ответы [ 6 ]

23 голосов
/ 13 сентября 2011

Существует множество приложений для не типовых аргументов шаблона;Вот некоторые из них:

Вы можете использовать не типовые аргументы для реализации универсальных типов, представляющих массивы или матрицы фиксированного размера.Например, вы можете параметризовать тип Matrix по его размерам, чтобы вы могли создать Matrix<4, 3> или Matrix<2, 2>.Если затем вы правильно определите перегруженные операторы для этих типов, вы можете предотвратить случайное добавление или умножение матриц неправильных измерений и создать функции, которые явно сообщают ожидаемые измерения матриц, которые они принимают.Это предотвращает возникновение огромного класса ошибок во время выполнения, обнаруживая нарушения во время компиляции.

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

template <unsigned n> struct Factorial {
    enum { 
       result = n * Factorial<n - 1>::result
    };
};
template <> struct Factorial<0> {
    enum {
       result = 1
    };
};

Это позволяет вам писать код наподобие Factorial<10>::result, чтобы получить во время компиляции значение 10 !.Это может предотвратить выполнение дополнительного кода во время выполнения.

Кроме того, вы можете использовать не типовые аргументы для реализации размерного анализа во время компиляции , который позволяет вам определять типы для килограммов, метров, секунди т. д., так что компилятор может гарантировать, что вы случайно не используете килограммы там, где вы имели в виду метры и т. д.

Надеюсь, это поможет!

7 голосов
/ 13 сентября 2011

Вы, вероятно, правы в этом случае, но есть случаи, когда вам нужно знать эту информацию во время компиляции:

Но как насчет этого?

template <std::size_t N>
std::array<int, N> get_array() { ... }

std::arrayнеобходимо знать его размер во время компиляции (как он расположен в стеке).

Вы не можете сделать что-то вроде этого:

std::array<int>(5);
5 голосов
/ 13 сентября 2011

Ну, это типичный выбор между полиморфизмом во время компиляции и полиморфизмом во время выполнения.

Из формулировки вашего вопроса видно, что вы не видите ничего необычного в «обычных» параметрах шаблона, воспринимая нетиповые параметры как что-то странное и / или избыточное.В действительности та же проблема может быть применена и к параметрам шаблона type (которые вы называли «обычными» параметрами).Идентичные функциональные возможности часто могут быть реализованы либо с помощью полиморфных классов с виртуальными функциями (полиморфизм во время выполнения), либо с помощью параметров шаблона type (полиморфизм во время компиляции).Можно также спросить, почему нам нужны параметры шаблона type , поскольку практически все можно реализовать с помощью полиморфных классов.

В случае нетипичных параметров вы можете захотетьиметь что-то подобное в один прекрасный день

template <int N> void foo(char (&array)[N]) {
  ...
}

, что невозможно реализовать с помощью значения времени выполнения.

5 голосов
/ 13 сентября 2011

В данном конкретном случае нет никаких преимуществ.Но используя такие параметры шаблона, вы можете делать много вещей, которые вы не могли бы сделать иначе, например, эффективно привязывать переменные к функциям (например, boost::bind), указывать размер массива времени компиляции в функции или классе (std::array является готовым примером этого) и т. Д.

Например, с помощью этой функции вы пишете функцию, такую ​​как

template<typename T>
void apply(T f) {
    f(somenum);
}

Затем вы можете передать apply функцию:

apply(&add<23>);

Это очень простой пример, но он демонстрирует принцип.Более продвинутые приложения включают в себя применение функций к каждому значению в коллекции, вычисление таких факторов, как факториал функции во время компиляции и т. Д.

Вы не могли бы сделать это любым другим способом.

1 голос
/ 13 сентября 2011

Наиболее частое использование для параметра значения, о котором я могу думать, это std::get<N>, который извлекает N-й элемент из std::tuple<Args...>.Вторым по частоте использования будет std::integral_constant и его основные производные std::true_type и std::false_type, которые повсеместны в любых классах признаков.Фактически, черты типа абсолютно изобилуют параметрами шаблона значения.В частности, существуют методы SFINAE, которые используют шаблон подписи <typename T, T> для проверки существования члена класса.

1 голос
/ 13 сентября 2011

Существует множество причин, например, метапрограммирование шаблонов (проверьте Boost.MPL).Но нет необходимости заходить так далеко, C ++ 11 std::tuple имеет метод доступа std::get<i>, который необходимо индексировать во время компиляции, поскольку результат зависит от индекса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...