Als Template-Argument übergebene Funktion

Lesezeit: 11 Minuten

Als Template Argument ubergebene Funktion
SPWorley

Ich suche nach den Regeln, die das Übergeben von C++-Vorlagenfunktionen als Argumente beinhalten.

Dies wird von C++ unterstützt, wie hier ein Beispiel zeigt:

#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>();
}

Das Erlernen dieser Technik ist jedoch schwierig. Googeln nach “Funktion als Vorlagenargument” bringt nicht viel. Und das klassische C++ Templates The Complete Guide geht überraschenderweise auch nicht darauf ein (zumindest nicht aus meiner Suche).

Die Fragen, die ich habe, sind, ob dies gültiges C++ ist (oder nur eine weithin unterstützte Erweiterung).

Gibt es auch eine Möglichkeit, einen Funktor mit derselben Signatur während dieser Art von Vorlagenaufruf austauschbar mit expliziten Funktionen zu verwenden?

Folgendes tut es nicht Arbeit in dem oben genannten Programm, zumindest in Visual C++, weil die Syntax offensichtlich falsch ist. Es wäre schön, eine Funktion für einen Funktor austauschen zu können und umgekehrt, ähnlich wie Sie einen Funktionszeiger oder Funktor an den std::sort-Algorithmus übergeben können, wenn Sie eine benutzerdefinierte Vergleichsoperation definieren möchten.

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

    doOperation<add3>();

Verweise auf ein oder zwei Weblinks oder eine Seite im C++-Vorlagenbuch wären willkommen!

  • Was ist der Vorteil einer Funktion als Template-Argument? Würde der Rückgabetyp nicht als Vorlagentyp verwendet werden?

    – DaClown

    23. Juli 2009 um 21:04 Uhr

  • Verwandt: Ein Lambda ohne Captures kann zu einem Funktionszeiger zerfallen, und Sie können das als Template-Parameter in C++17 übergeben. Clang kompiliert es in Ordnung, aber der aktuelle gcc (8.2) hat einen Fehler und weist ihn sogar mit fälschlicherweise als “keine Verknüpfung” zurück -std=gnu++17. Kann ich das Ergebnis eines C++17 Captureless Lambda constexpr Konvertierungsoperators als Nicht-Typ-Argument einer Funktionszeigervorlage verwenden?

    – Peter Cordes

    7. August 2018 um 22:41 Uhr

1646918412 407 Als Template Argument ubergebene Funktion
jalf

Ja, es ist gültig.

Damit es auch mit Funktoren funktioniert, sieht die übliche Lösung stattdessen so aus:

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

die jetzt aufgerufen werden kann als:

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

Sehen Sie es live

Das Problem dabei ist, dass es für den Compiler schwierig wird, den Aufruf zu inline add2da der Compiler nur weiß, dass es sich um einen Funktionszeigertyp handelt void (*)(int &) wird weitergegeben doOperation. (Aber add3, da es sich um einen Funktor handelt, kann leicht inliniert werden. Hier weiß der Compiler, dass ein Objekt vom Typ add3 an die Funktion übergeben wird, was bedeutet, dass die Funktion aufgerufen werden soll add3::operator()und nicht nur ein unbekannter Funktionszeiger.)

  • Hier ist nun eine interessante Frage. Wenn ein Funktionsname übergeben wird, ist es NICHT so, als wäre ein Funktionszeiger beteiligt. Es ist eine explizite Funktion, die zur Kompilierzeit angegeben wird. Der Compiler weiß also genau, was er zur Kompilierzeit hat.

    – SPWorley

    23. Juli 2009 um 20:27 Uhr

  • Es gibt einen Vorteil bei der Verwendung von Funktoren gegenüber Funktionszeigern. Der Funktor kann innerhalb der Klasse instanziiert werden und bietet dem Compiler somit mehr Möglichkeiten für Optimierungen (z. B. Inlining). Der Compiler würde sich schwer tun, einen Aufruf über einen Funktionszeiger zu optimieren.

    – Martin York

    23. Juli 2009 um 20:41 Uhr

  • Wenn die Funktion in einem Vorlagenparameter verwendet wird, ‘zerfällt’ sie in einen Zeiger auf die übergebene Funktion. Es ist analog dazu, wie Arrays in Zeiger zerfallen, wenn sie als Argumente an Parameter übergeben werden. Natürlich ist der Zeigerwert zur Kompilierzeit bekannt und muss auf eine Funktion mit externer Verknüpfung zeigen, damit der Compiler diese Informationen zu Optimierungszwecken verwenden kann.

    – CB Bailey

    23. Juli 2009 um 20:43 Uhr

  • Spulen Sie einige Jahre später vor, die Situation mit der Verwendung von Funktionen als Vorlagenargumente hat sich in C++ 11 stark verbessert. Sie sind nicht mehr an die Verwendung von Javaismen wie Funktorklassen gebunden und können beispielsweise statische Inline-Funktionen direkt als Template-Argumente verwenden. Im Vergleich zu Lisp-Makros der 1970er Jahre immer noch weit entfernt, aber C++ 11 hat im Laufe der Jahre definitiv gute Fortschritte gemacht.

    – pfalke

    13. April 2013 um 15:04 Uhr


  • seit c++11 wäre es nicht besser die funktion als rvalue referenz zu nehmen (template <typename F> void doOperation(F&& f) {/**/}), also kann bind beispielsweise einen Bindungsausdruck übergeben, anstatt ihn zu binden?

    – Benutzer1810087

    8. Januar 2015 um 16:59 Uhr


Vorlagenparameter können entweder nach Typ (typename T) oder nach Wert (int X) parametrisiert werden.

Die “traditionelle” C++-Art, einen Codeabschnitt mit Vorlagen zu versehen, besteht darin, einen Funktor zu verwenden – das heißt, der Code befindet sich in einem Objekt, und das Objekt gibt dem Code somit einen eindeutigen Typ.

Bei der Arbeit mit traditionellen Funktionen funktioniert diese Technik nicht gut, da eine Änderung des Typs nicht auf a hinweist Spezifisch Funktion – sondern spezifiziert nur die Signatur vieler möglicher Funktionen. Damit:

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

Entspricht nicht dem Fall des Funktors. In diesem Beispiel wird do_op für alle Funktionszeiger instanziiert, deren Signatur int X (int, int) ist. Der Compiler müsste ziemlich aggressiv sein, um diesen Fall vollständig zu inlinen. (Ich würde es jedoch nicht ausschließen, da die Compiler-Optimierung ziemlich weit fortgeschritten ist.)

Eine Möglichkeit zu sagen, dass dieser Code nicht ganz das tut, was wir wollen, ist:

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

ist immer noch legal, und dies wird eindeutig nicht inliniert. Um vollständiges Inlining zu erhalten, müssen wir die Vorlage nach Wert erstellen, damit die Funktion in der Vorlage vollständig verfügbar ist.

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

In diesem Fall wird jede instanziierte Version von do_op mit einer bereits verfügbaren spezifischen Funktion instanziiert. Daher erwarten wir, dass der Code für do_op sehr ähnlich aussieht wie “return a + b”. (Programmierer, hör auf zu grinsen!)

Wir können auch bestätigen, dass dies näher an dem liegt, was wir wollen, denn dies:

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

wird nicht kompiliert. GCC sagt: “Fehler: ‘func_ptr’ kann nicht in einem konstanten Ausdruck erscheinen. Mit anderen Worten, ich kann do_op nicht vollständig erweitern, weil Sie mir zur Compilerzeit nicht genügend Informationen gegeben haben, um zu wissen, was unsere Operation ist.

Wenn also das zweite Beispiel unsere Operation wirklich vollständig einbettet und das erste nicht, was nützt dann die Vorlage? Was macht es? Die Antwort lautet: Geben Sie Zwang ein. Dieses Riff im ersten Beispiel funktioniert:

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

Dieses Beispiel wird funktionieren! (Ich behaupte nicht, dass es gutes C++ ist, aber …) Was passiert ist, ist, dass do_op um die herum erstellt wurde Unterschriften der verschiedenen Funktionen, und jede separate Instanziierung schreibt einen anderen Typ von Zwangscode. Der instanziierte Code für do_op mit fadd sieht also etwa so aus:

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.

Im Vergleich dazu erfordert unser By-Value-Fall eine exakte Übereinstimmung der Funktionsargumente.

  • Siehe stackoverflow.com/questions/13674935/… für eine Folgefrage als direkte Antwort auf die Beobachtung hier, dass int c = do_op(4,5,func_ptr); ist “eindeutig nicht inline”.

    – Dan Nissenbaum

    3. Dezember 2012 um 9:03 Uhr

  • Hier finden Sie ein Beispiel dafür, dass dies inline ist: stackoverflow.com/questions/4860762/… Scheint, dass Compiler heutzutage ziemlich schlau werden.

    – Großes Sandwich

    16. August 2013 um 14:07 Uhr


1646918412 828 Als Template Argument ubergebene Funktion
Kietz

Funktionszeiger können als Vorlagenparameter übergeben werden, und dies ist Teil des Standard-C++
. In der Vorlage werden sie jedoch als Funktionen und nicht als Zeiger auf Funktionen deklariert und verwendet. Bei Vorlage Instanziierung Man übergibt die Adresse der Funktion und nicht nur den Namen.

Zum Beispiel:

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

Wenn Sie einen Funktortyp als Vorlagenargument übergeben möchten:

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

Mehrere Antworten übergeben eine Funktorinstanz als 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);

Am nächsten kommt man diesem einheitlichen Erscheinungsbild mit einem Template-Argument, indem man definiert do_op zweimal – einmal mit einem Nicht-Typ-Parameter und einmal mit einem Typ-Parameter.

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

Ehrlich gesagt, ich Ja wirklich Ich hatte erwartet, dass dies nicht kompiliert wird, aber es hat bei mir mit gcc-4.8 und Visual Studio 2013 funktioniert.

In deiner Vorlage

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

Der Parameter T ist ein Vorlagenparameter, der kein Typ ist. Dies bedeutet, dass sich das Verhalten der Vorlagenfunktion mit dem Wert des Parameters ändert (der zur Kompilierzeit festgelegt werden muss, welche Funktionszeigerkonstanten sind).

Wenn Sie etwas wollen, das sowohl mit Funktionsobjekten als auch mit Funktionsparametern funktioniert, benötigen Sie eine typisierte Vorlage. Wenn Sie dies tun, müssen Sie der Funktion jedoch zur Laufzeit auch eine Objektinstanz (entweder eine Funktionsobjektinstanz oder einen Funktionszeiger) bereitstellen.

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

Es gibt einige kleinere Überlegungen zur Leistung. Diese neue Version ist möglicherweise weniger effizient mit Funktionszeiger-Argumenten, da der bestimmte Funktionszeiger nur derefferiert und zur Laufzeit aufgerufen wird, während Ihre Funktionszeiger-Vorlage basierend auf dem bestimmten verwendeten Funktionszeiger optimiert werden kann (möglicherweise der Funktionsaufruf inline). Funktionsobjekte können oft sehr effizient mit der typisierten Vorlage erweitert werden, allerdings als das Besondere operator() wird vollständig durch den Typ des Funktionsobjekts bestimmt.

Der Grund, warum Ihr Funktor-Beispiel nicht funktioniert, ist, dass Sie eine Instanz benötigen, um die aufzurufen operator().

Kam hier mit der zusätzlichen Anforderung, dass auch Parameter/Rückgabetypen variieren sollten. Nach Ben Supnik wäre dies für einen Typ T

typedef T(*binary_T_op)(T, T);

anstatt

typedef int(*binary_int_op)(int, int);

Die Lösung besteht hier darin, die Funktionstypdefinition und das Funktions-Template in ein umgebendes Struct-Template zu packen.

template <typename T> struct BinOp
{
    typedef T(*binary_T_op )(T, T); // signature for all valid template params
    template<binary_T_op op>
    T do_op(T a, T b)
    {
       return op(a,b);
    }
};


double mulDouble(double a, double b)
{
    return a * b;
}


BinOp<double> doubleBinOp;

double res = doubleBinOp.do_op<&mulDouble>(4, 5);

Alternativ könnte BinOp eine Klasse mit statischem Methoden-Template do_op(…) sein, die dann als aufgerufen wird

double res = BinOp<double>::do_op<&mulDouble>(4, 5);

BEARBEITEN

Inspiriert durch den Kommentar von 0x2207 ist hier ein Funktor, der eine beliebige Funktion mit zwei Parametern und konvertierbaren Werten übernimmt.

struct BinOp
{
    template <typename R, typename S, typename T, typename U, typename V> R operator()(R (*binaryOp )(S, T), U u, V v)
    {
        return binaryOp(u,v);
    }

};

double subD(double a, int b)
{
    return a-b;
}

int subI(double a, int b)
{
    return (int)(a-b);
}


int main()
{
    double resD = BinOp()(&subD, 4.03, 3);
    int resI = BinOp()(&subI, 4.03, 3);

    std::cout << resD << std::endl;
    std::cout << resI << std::endl;
    return 0;
}

richtig auswertet doppelt 1.03 und int 1

Als Template Argument ubergebene Funktion
Ravi

Bearbeiten: Das Übergeben des Operators als Referenz funktioniert nicht. Verstehen Sie es der Einfachheit halber als Funktionszeiger. Sie senden nur den Zeiger, keine Referenz. Ich glaube, du versuchst, so etwas zu schreiben.

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;

988070cookie-checkAls Template-Argument übergebene Funktion

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy