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

Я ищу правила, связанные с передачей функций шаблонов 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 ответов


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

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

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.

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


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

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;