Funkcja przekazywana jako argument szablonu

Szukam reguł dotyczących przekazywania funkcji szablonów C++ jako argumentów.

Jest to obsługiwane przez C++, jak pokazano na przykładzie tutaj:

#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>();
}
Poznanie tej techniki jest jednak trudne. Googlowanie dla "function as a template argument" nie prowadzi do Wiele. A Klasyczne Szablony C++ kompletny przewodnik o dziwo też o tym nie dyskutuje (przynajmniej nie z moich poszukiwań).

Mam pytanie czy jest to poprawne C++ (czy tylko jakieś szeroko obsługiwane rozszerzenie).

Czy istnieje sposób, aby funktor o tym samym podpisie mógł być używany zamiennie z jawnymi funkcjami podczas tego rodzaju wywoływania szablonu?

Poniżej Nie Działa w powyższym programie, przynajmniej w Visual C++, ponieważ składnia jest oczywiście błędna. Byłoby miło móc przełączyć funkcję na funktor i odwrotnie, podobnie jak można przekazać wskaźnik funkcji lub funktor do algorytm sortowania std::jeśli chcesz zdefiniować niestandardową operację porównywania.

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

    doOperation<add3>();

Wskaźniki do linku lub dwóch stron lub strony w książce szablonów C++ będą mile widziane!

Author: Peter Mortensen, 2009-07-24

6 answers

Tak, to jest ważne.

Jeśli chodzi o to, że działa również z funktorami, Zwykle rozwiązaniem jest coś takiego:

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

Które można teraz nazwać albo:

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

Problem polega na tym, że jeśli to utrudnia kompilatorowi wbudowanie wywołania do add2, ponieważ kompilator wie tylko, że wskaźnik funkcji void (*)(int &) jest przekazywany do doOperation. (Ale add3, będąc funktorem, można łatwo inlinować. Tutaj kompilator wie, że obiekt typu add3 jest przekazywana do funkcji, co oznacza, że Funkcją do wywołania jest add3::operator(), a nie tylko jakimś nieznanym wskaźnikiem funkcji.)

 108
Author: jalf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-23 20:12:14

Parametry szablonu mogą być parametryzowane przez typ (typename T) lub przez wartość (int X).

"tradycyjnym" sposobem tworzenia szablonów w C++ jest użycie functora - to znaczy, że kod znajduje się w obiekcie, a tym samym obiekt nadaje kodowi unikalny typ.

Podczas pracy z tradycyjnymi funkcjami, ta technika nie działa dobrze, ponieważ zmiana typu nie wskazuje konkretnej funkcji - raczej określa tylko sygnaturę wielu możliwych funkcji. Wię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);

Nie jest odpowiednikiem przypadku functora. W tym przykładzie, do_op jest tworzona instancja dla wszystkich wskaźników funkcji, których sygnatura to int X (int, int). Kompilator musiałby być dość agresywny, aby w pełni zintegrować tę sprawę. (Nie wykluczałbym tego jednak, ponieważ optymalizacja kompilatora stała się dość zaawansowana.)

Jednym ze sposobów, aby powiedzieć, że ten kod nie robi tego, czego chcemy, jest:

int (* func_ptr)(int, int) = add;
int c = do_op(4,5,func_ptr);

Jest nadal legalne, i najwyraźniej nie jest to wkomponowane. Aby uzyskać pełny inlining, musimy szablon według wartości, więc funkcja jest w pełni dostępna w szablonie.

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

W tym przypadku, każda instancyjna wersja do_op jest tworzona z określoną funkcją już dostępną. Dlatego oczekujemy, że kod do_op będzie wyglądał podobnie do "return A + b". (Programiści Lispu, przestańcie się uśmiechać!)

Możemy również potwierdzić, że jest to bliższe temu, czego chcemy, ponieważ to:

int (* func_ptr)(int,int) = add;
int c = do_op<func_ptr>(4,5);

Nie uda się skompilować. GCC mówi: "błąd: 'func_ptr' nie może pojawić się w wyraz stały. Innymi słowy, nie mogę w pełni rozwinąć do_op, ponieważ nie podałeś mi wystarczająco dużo informacji w czasie kompilatora, aby wiedzieć, co to jest nasz op.

Więc jeśli drugi przykład naprawdę w pełni wpisuje się w naszą op, a pierwszy nie, to po co szablon? Co to robi? Odpowiedź brzmi: rodzaj przymusu. Ten riff na pierwszym przykładzie zadziała:

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

Ten przykład zadziała! (Nie sugeruję, że jest to dobry C++, ale...) Co się stało, to do_op został szablonem wokół sygnatur różnych funkcji, a każda osobna instancja będzie pisać inny kod typu. Tak więc instancyjny kod do_op z fadd wygląda mniej więcej tak:

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.

Dla porównania, nasz przypadek by-value wymaga dokładnego dopasowania argumentów funkcji.

 58
Author: Ben Supnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-22 15:46:04

Wskaźniki funkcji mogą być przekazywane jako parametry szablonu, a jest to część standardowego C++ . Jednak w szablonie są deklarowane i używane jako funkcje, a nie wskaźnik do funkcji. W template instantiation podaje się adres funkcji, a nie tylko nazwę.

Na przykład:

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

Jeśli chcesz przekazać Typ functora jako argument szablonu:

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

Kilka odpowiedzi przekazuje instancję functora jako argument:

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

Najbliżej tego jednolitego wyglądu z argumentem szablonu jest zdefiniowanie do_op dwa razy-raz z parametrem nie-type I raz z parametrem type.

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

Szczerze mówiąc, janaprawdę spodziewałem się, że nie będzie kompilacji, ale zadziałało dla mnie z gcc-4.8 i Visual Studio 2013.

 11
Author: Kietz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-16 14:33:19

W szablonie

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

Parametr T nie jest parametrem szablonu typu. Oznacza to, że zachowanie funkcji szablonu zmienia się wraz z wartością parametru(który musi być ustalony w czasie kompilacji, jakie są stałe wskaźnika funkcji).

Jeśli chcesz coś, co działa zarówno z obiektami funkcyjnymi, jak i z parametrami funkcji, potrzebujesz wpisanego szablonu. Gdy to zrobisz, musisz również podać instancję obiektu (instancję obiektu funkcji lub wskaźnik funkcji) do funkcji w czasie wykonywania.

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

Istnieją pewne drobne względy wydajności. Ta nowa wersja może być mniej wydajna z argumentami wskaźnika funkcji, ponieważ konkretny wskaźnik funkcji jest tylko derefenced i wywołany w czasie wykonywania, podczas gdy szablon wskaźnika funkcji może być zoptymalizowany (ewentualnie wywołanie funkcji inlined) na podstawie konkretnego wskaźnika funkcji użytego. Obiekty funkcyjne można często bardzo efektywnie rozbudowywać za pomocą wpisanego szablonu, choć jako szczególna operator() jest całkowicie określona przez typ obiektu funkcji.

 8
Author: CB Bailey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-23 20:18:34

Powodem, dla którego twój przykład functora nie działa, jest to, że potrzebujesz instancji do wywołania operator().

 1
Author: Nikolai Fetissov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-23 20:12:35

Edit: podanie operatora jako referencji nie działa. Dla uproszczenia, zrozum to jako wskaźnik funkcji. Po prostu wysyłasz wskaźnik, a nie referencję. Myślę, że próbujesz napisać coś takiego.

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
Author: AraK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-15 12:59:19