Функция передала как аргумент шаблона

Стандарт C определяет [] оператор следующим образом:

a[b] == *(a + b)

Поэтому a[5] оценит к:

*(a + 5)

и 5[a] оценит к:

*(5 + a)

a указатель на первый элемент массива. a[5] значение, этому 5 лет элементы далее от a, который совпадает с *(a + 5), и от математики начальной школы мы знаем, что те равны (дополнение коммутативное ).

216
задан Peter Mortensen 23 June 2015 в 12:00
поделиться

5 ответов

Да, это верно.

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

template <typename F>
void doOperation(F f)
{
  int temp=0;
  f(temp);
  std::cout << "Result is " << temp << std::endl;
}

которое теперь может называться либо:

doOperation(add2);
doOperation(add3());

Проблема заключается в том, что компилятору сложно встроить вызов add2 , поскольку все, что знает компилятор, - это тип указателя на функцию void (*) (int & ) передается в doOperation . (Но add3 , будучи функтором, может быть легко встроен. Здесь компилятор знает, что объект типа add3 передается функции, что означает, что вызываемая функция add3 :: operator () , а не просто указатель на неизвестную функцию.)

120
ответ дан 23 November 2019 в 04:19
поделиться

В вашем шаблоне

template <void (*T)(int &)>
void doOperation()

Параметр T не является параметром шаблона типа. Это означает, что поведение функции шаблона изменяется в зависимости от значения параметра (которое должно быть исправлено во время компиляции, каковыми являются константы указателя функции).

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

template <class T>
void doOperation(T t)
{
  int temp=0;
  t(temp);
  std::cout << "Result is " << temp << std::endl;
}

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

9
ответ дан 23 November 2019 в 04:19
поделиться

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

1
ответ дан 23 November 2019 в 04:19
поделиться

Изменить: передача оператора в качестве ссылки не работает. Для простоты понимайте это как указатель на функцию. Вы просто отправляете указатель, а не ссылку. Я думаю, вы пытаетесь написать что-то вроде этого

struct Square
{
    double operator()(double number) { return number * number; }
};

template <class Function>
double integrate(Function f, double a, double b, unsigned int intervals)
{
    double delta = (b - a) / intervals, sum = 0.0;

    while(a < b)
    {
        sum += f(a) * delta;
        a += delta;
    }

    return sum;
}

. .

std::cout << "interval : " << i << tab << tab << "intgeration = "
 << integrate(Square(), 0.0, 1.0, 10) << std::endl;
0
ответ дан 23 November 2019 в 04:19
поделиться

Параметры шаблона могут быть параметризованы как по типу (имя типа T), так и по значению (int X).

"Традиционный" С++ способ шаблонирования фрагмента кода заключается в использовании functor - то есть код находится в объекте, и таким образом объект дает код уникального типа.

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

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

не эквивалентна регистру functor. В данном примере 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.

Для сравнения, наш случай by-значения требует точного совпадения аргументов функции.

66
ответ дан 23 November 2019 в 04:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: