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


Я ищу правила, связанные с передачей функций шаблонов C++ в качестве аргументов.

это поддерживается C++ , как показано на примере здесь:

#include <iostream>

void add1(int &v)
{
  v+=1;
}

void add2(int &v)
{
  v+=2;
}

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

int main()
{
  doOperation<add1>();
  doOperation<add2>();
}

изучение этой техники трудно, однако. Googling для "функции в качестве аргумента шаблона" не приводит к большому. И классический Шаблоны C++ Полное Руководство удивительно и не обсуждать (по крайней мере не из моего поиска).

вопросы I есть ли это допустимый C++ (или просто какое-то широко поддерживаемое расширение).

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

ниже ли не работу в этой программе, по крайней мере в Visual C++, потому что синтаксис явно неправильный. Было бы неплохо иметь возможность переключать функцию для функтора и наоборот, аналогично тому, как вы можете передать указатель функции или функтор алгоритму std::sort, если вы хотите определить пользовательскую операцию сравнения.

   struct add3 {
      void operator() (int &v) {v+=3;}
   };
...

    doOperation<add3>();

указатели на веб-ссылку или две, или страницу в книге шаблонов C++ будут оценены!

6 194

6 ответов:

Да, действительно.

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

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(), а не просто какой-то неизвестный указатель на функцию.)

параметры шаблона могут быть параметризованы по типу (typename 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, потому что вы не дали мне достаточно информации во время компилятора, чтобы знать, что такое наша операция.

Итак, если второй пример действительно полностью встраивает наш 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.

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

указатели на функцию могут быть переданы в качестве параметров шаблона, и это часть стандартного C++ . Однако в шаблоне они объявлены и используются как функции, а не указатель на функцию. По шаблону экземпляров один передает адрес функции, а не просто название.

например:

int i;


void add1(int& i) { i += 1; }

template<void op(int&)>
void do_op_fn_ptr_tpl(int& i) { op(i); }

i = 0;
do_op_fn_ptr_tpl<&add1>(i);

если вы хотите передать тип функтор в качестве аргумента шаблона:

struct add2_t {
  void operator()(int& i) { i += 2; }
};

template<typename op>
void do_op_fntr_tpl(int& i) {
  op o;
  o(i);
}

i = 0;
do_op_fntr_tpl<add2_t>(i);

несколько ответов передают функтор экземпляр в качестве аргумента:

template<typename op>
void do_op_fntr_arg(int& i, op o) { o(i); }

i = 0;
add2_t add2;

// This has the advantage of looking identical whether 
// you pass a functor or a free function:
do_op_fntr_arg(i, add1);
do_op_fntr_arg(i, add2);

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

// non-type (function pointer) template parameter
template<void op(int&)>
void do_op(int& i) { op(i); }

// type (functor class) template parameter
template<typename op>
void do_op(int& i) {
  op o; 
  o(i); 
}

i = 0;
do_op<&add1>(i); // still need address-of operator in the function pointer case.
do_op<add2_t>(i);

Честно Говоря, Я действительно ожидал, что это не будет компилироваться, но он работал для меня с gcc-4.8 и Visual Studio 2013.

в шаблоне

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() полностью определяется типом объекта функции.

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

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

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;