Что хорошего в использовании нетиповых параметров шаблона? [Дубликат]

0

Известно, что в C++ мы можем иметь несимметричные параметры шаблона, такие как int:

template <class T, int size>
void f(){...}

Интересно, как он отличается от обычного способа передачи параметра в функцию:

template <class T>
void f(int size) {...}

Я считаю, что одно отличие заключается в том, что size шаблонов оценивается во время компиляции и заменяется как литералы при создании экземпляра шаблона. Таким образом, я сомневаюсь (исправьте меня, если я ошибаюсь), что каждое другое значение size приводит к созданию новых двоичных кодов (".text"), что, по-видимому, является накладными расходами.

Может ли кто-нибудь сказать, когда это необходимо и стоит?

  • 0
    Это очень необходимо для std::array .
  • 0
    Компилирует ли? Как компилятор определяет T ?
Показать ещё 5 комментариев
Теги:
templates

4 ответа

3
Лучший ответ

Таким образом, я сомневаюсь (исправьте меня, если я ошибаюсь), что каждое другое значение размера приводит к созданию новых двоичных кодов (".text"), что, по-видимому, является накладными расходами.

Это на самом деле, и это общий источник раздувания кода. Вам нужно выяснить, когда вы хотите генерировать разные функции для каждого N и когда вам нужна одна функция, в которой у компилятора меньше информации (обратите внимание, что это не только для производительности, но и для правильности).

Поскольку Мэтт уже привел простой пример, позволяет работать над функцией, которая принимает массив по ссылке:

template<typename T, size_t N>
size_t operateOnArray( T (&array)[N] )
{
     // Some complex logic, which could include:
    for (std::size_t i = 0; i < N; ++i) {
       // complicated stuff
    }
}

Тип аргумента является ссылкой на массив, компилятор проверяет для вас, что массив действительно имеет N элементов (и он будет выводить тип значений в массиве). Это отличное улучшение безопасности типов по сравнению с аналогичным кодом стиля C:

size_t operateOnArray( T *array, size_t N)
{
     // Some complex logic, which could include:
    for (std::size_t i = 0; i < N; ++i) {
       // complicated stuff
    }
}

В частности, пользователь может ошибочно передать неправильное значение:

int array[10];
operateOnArray(arrah, 20); // typo!!!

Где в первом случае компилятор выведет размер, и это будет гарантировать, что это правильно.

Вы попадаете в гвоздь в голове, когда вы упомянули, что это потенциально может добавить к размеру кода и что оно может добавить довольно много. Представьте себе, что эта функция достаточно сложна, чтобы она не входила в рамки, и представьте, что в вашей программе вы вызываете функцию со всеми размерами от 1 до 100. Программный код будет содержать 100 экземпляров в основном того же кода, где единственная разница это размер.

Есть решения вокруг этого, как смешивание двух подходов:

size_t operateOnArray( T *array, size_t N); // Possibly private, different name...
template<typename T, size_t N>
size_t operateOnArray( T (&array)[N] ) {
   operateOnArray(array, N);
}

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

Может ли кто-нибудь сказать, когда это необходимо и стоит?

Это необходимо, когда код внутри шаблона требует значения как константы времени компиляции, например, в приведенном выше коде, вы не можете иметь аргумент функции, который является ссылкой на массив из N элементов, где N доступен только во время выполнения. В других случаях, например std::array<T,N> требуется статически создавать массив соответствующего размера. Независимо от того, что, все примеры разделили это: значение должно быть известно во время компиляции.

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

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

  • 0
    Дэвид, спасибо за исчерпывающий ответ. Но я немного запутался с предложением «компилятор проверит для вас, что массив действительно содержит N элементов». Насколько мне известно, когда вы передаете массив функции, функция не заботится о ее размере. Скажем, f(int &a[4]) - это то же самое, что f(int &a[]) . Как это работает здесь?
  • 0
    @ Neo1989 f(int* a) эквивалентно f(int a[]) и f(int a[42]) , но Дэвид использует ссылку на массив, который отличается: f(int (&a)[42]) .
Показать ещё 1 комментарий
0

Анализ размерности во времени компиляции. Это помогло мне обнаружить ошибки на ранней стадии внедрения физических симуляций.

0

Помимо обеспечения большей возможности оптимизации, параметры могут быть задействованы в выводе шаблона (в отличие от аргументов функции), например, это общий метод определения количества элементов в именованном массиве:

template<typename T, size_t N>
size_t lengthof( T (&array)[N] )
{
     return N;
}

Применение:

#include <iostream>

int main()
{
    wchar_t foo[] = L"The quick brown fox";
    std::wcout << "\"" << foo << "\" has " << lengthof(foo) - 1 << " characters.\n";
}

Вероятно, компилятор рассчитает длину в compiletime и заменит это непосредственно на wcout <<......., даже не wcout <<....... вызов функции времени исполнения.

0

Передача размера в качестве параметра шаблона непигового типа необходима, когда f() хочет выделить массив C-стиля такого размера (например, как f(){int array[size]; }). Если вы передадите размер как параметр функции, ваша программа не будет компилироваться, потому что размер неизвестен во время компиляции.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню