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



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


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

У меня есть вопросы, Является ли это допустимым C++ (или просто некоторые широко поддерживаемые расширения).



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



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



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

doOperation<add3>();


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

640   6  

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". (Шепелявые программисты, прекратите ухмыляться!)

Мы также можем подтвердить, что это ближе к тому, что мы хотим, потому что это:

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().

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;

Comments

    Ничего не найдено.