Параметры шаблона могут быть параметризованы либо по типу (имя типа T), либо по значению (int X).
«Традиционный» способ создания шаблона в C ++ для фрагмента кода - использовать функтор, то есть код находится в объекте, и объект, таким образом, придает коду уникальный тип.
При работе с традиционными функциями этот метод не работает, потому что изменение типа не указывает на конкретную функцию, а указывает только сигнатуру многих возможных функций. Так:
template<typename OP>
int do_op(int a, int b, OP op)
{
return op(a,b);
}
int add(int a, int b) { return a + b; }
...
int c = do_op(4,5,add);
Не эквивалентно случаю функтора. В этом примере do_op создается для всех указателей на функции, подпись которых - int X (int, int). Компилятор должен быть довольно агрессивным, чтобы полностью встроить этот случай. (Я не исключаю этого, поскольку оптимизация компилятора стала довольно продвинутой.)
Один из способов сказать, что этот код не совсем выполняет то, что мы хотим:
int (* func_ptr)(int, int) = add;
int c = do_op(4,5,func_ptr);
все еще законно, и очевидно, что это не встраивается. Чтобы получить полное встраивание, нам нужно использовать шаблон по значению, поэтому функция полностью доступна в шаблоне.
typedef int(*binary_int_op)(int, int); // signature for all valid template params
template<binary_int_op op>
int do_op(int a, int b)
{
return op(a,b);
}
int add(int a, int b) { return a + b; }
...
int c = do_op<add>(4,5);
В этом случае каждая инстанцируемая версия do_op инстанцируется с определенной уже доступной функцией. Таким образом, мы ожидаем, что код для do_op будет очень похож на "return a + b". (Программисты Lisp, перестаньте ухмыляться!)
Мы также можем подтвердить, что это ближе к тому, что мы хотим, потому что это:
int (* func_ptr)(int,int) = add;
int c = do_op<func_ptr>(4,5);
не скомпилируется. GCC говорит: «ошибка: 'func_ptr' не может появиться в константном выражении. Другими словами, я не могу полностью развернуть do_op, потому что вы не предоставили мне достаточно информации во время компиляции, чтобы узнать, какова наша операция.
Итак, если второй пример действительно полностью встраивает нашу операцию, а первый - нет, что хорошего в шаблоне? Что он делает? Ответ: принуждение типа. Этот рифф в первом примере будет работать:
template<typename OP>
int do_op(int a, int b, OP op) { return op(a,b); }
float fadd(float a, float b) { return a+b; }
...
int c = do_op(4,5,fadd);
Этот пример будет работать! (Я не утверждаю, что это хороший C ++, но ...) Произошло то, что do_op был шаблонизирован вокруг сигнатур различных функций, и каждый отдельный экземпляр будет писать другой код приведения типов. Таким образом, созданный экземпляр кода для do_op с fadd выглядит примерно так:
convert a and b from int to float.
call the function ptr op with float a and float b.
convert the result back to int and return it.
Для сравнения, наш случай по значению требует точного совпадения аргументов функции.
person
Community
schedule
28.01.2010
-std=gnu++17
. Могу ли я использовать результат оператора преобразования C ++ 17 лямбда-выражения constexpr без захвата в качестве аргумента, не являющегося типом шаблона указателя функции?. - person Peter Cordes   schedule 08.08.2018