2009-10-03 7 views
11

Beim Lesen einer Frage im Stackoverflow habe ich mich gefragt, ob es möglich ist, eine Funktion zu deklarieren, die einen Zeiger auf sich selbst nimmt. I.e.Kann ich eine Funktion deklarieren, die Zeiger auf sich selbst als Argument nehmen kann?

foo(foo); 

Die einfachste Idee auf eine andere Funktion Zeiger Gießen (kann nicht gegossen void*, da es kleiner sein kann), so die Funktionsdeklaration: solche Erklärung von foo, für die folgende wäre richtig zu machen sieht wie folgt aus:

void foo(void (*)()); 

Während das in C OK ist (und wird mit dem Gießen in C++ arbeiten), frage ich mich, ob es ohne eine solche harte „umdeuten“ Gießen oder verlieren Typinformationen durchgeführt werden kann.

Mit anderen Worten, ich möchte folgende Erklärung ab:

void foo(void (*)(void (*)(void (*) (...)))); 

aber natürlich sind unbegrenzt Erklärungen unmöglich. Naive typedef Ing hilft auch nicht.

C++ - Vorlagen sind willkommen, auch wenn sie den aufrufenden Code (foo(foo)) etwas weniger prägnant aussehen lassen, aber immer noch endlich.

C-style Antworten, die zeigen, wie man Typinformationen ohne Casting oder andere Tricks dieser Art fallen lassen kann, sind natürlich interessant, werden aber nicht akzeptiert.

+1

Gibt es eine funktionale Sprache, in der dies möglich ist? – Crashworks

+0

Es gibt nicht-funktionale Sprachen, in denen es möglich ist, z.B. C# –

+0

es ist trivial in jeder dynamisch-typisierten Sprache zu tun, z.B. Python, Ruby, Perl, PHP, JavaScript, um nur einige zu nennen ... – newacct

Antwort

2

Generell stimme ich mit Dario überein - dies auf Typenebene scheint unmöglich.

Aber Sie können Klassen verwenden ("Strategie-Muster"):

class A { 
    void evil(A a) {  // a pointer to A is ok too 
    } 
}; 

Sie können sogar operator() hinzufügen:

void operator()(A a) { return evil(a); } 

Im Allgemeinen sind solche Dinge besser in FP Sprachen erfolgen. Haskell-Version ist einfach:

data Evil = Evil (Evil -> Integer) 

Dieses verwendet einen Wrapper (Evil), die mit einer Klasse entspricht.

Aus dem Frager: was diese Antwort fehlte, war die Fähigkeit, mehrere verschiedene Funktionen als ein Argument von einem von ihnen zu übergeben. Dies kann entweder dadurch gelöst werden, dass evil() virtuell gemacht wird oder indem explizit im Objekt ein Funktionszeiger gespeichert wird, der es implementiert (was im Wesentlichen derselbe ist).

Mit dieser Klarstellung ist die Antwort gut genug, um akzeptiert zu werden.

+0

Beat mich dazu ... – DigitalRoss

+0

Schöne Idee. Aber ich möchte auch mehr Parameter zusammen mit dem Funktionszeiger übergeben. Ich sehe eine Möglichkeit, dies zu tun, indem man das Fabrikmuster verwendet, um "Funktionen" zu klonen und die Parameter als Konstruktorargumente zu übergeben. Aber das scheint zu knifflig ... –

+0

Wo genau übertriffst du diese Parameter? Mein naives Verständnis wäre void operator() (A a, int x, int y) - Sie können A als die Art von Funktionen betrachten, die A und zwei Ints nehmen und void zurückgeben. Haskells Version wäre Daten Evil = Evil (Evil -> Integer -> Ganze Zahl -> T) wobei T der Return-Typ ist. Habe ich dich verstanden? Das Fabrikmuster curryt in FP - es ist viel klarer in ML/Haskell, aber ich verstehe nicht, warum es hier gebraucht wird. Wenn möglich, erweitern Sie bitte die Frage mit einer Beispielanwendung dieser zusätzlichen Parameter (nicht unbedingt eine reale). – sdcvvc

5

Offensichtlich nicht - siehe this thread. Der hier benötigte Typ wäre immer unendlich.

+1

Ich sehe immer noch keine Beziehung zwischen "unendlichem Typ" und "möglich". Wenn ich dachte, dass es einen gibt, würde ich die Frage nicht stellen, oder? –

+1

Alle statisch typisierten Sprachen, die ich kenne, verlangen, dass ihre Typen endlich sind. Wenn ein Typ unendlich ist, kann das Typsystem diesen nicht ausdrücken. Aber mach weiter, erfinde ein unendliches System. – Dario

+0

Unendlich Typen sind nicht deklarierbar ...? Du hast es selbst gesagt, "unbegrenzte Erklärungen sind unmöglich". – GManNickG

0

Dies ist eine vollkommen gültige Verwendung von void *.

+0

Ah, ein gutes Beispiel für das, was ich "C-Style Trick" nenne. ;-) –

+6

eigentlich, das ist nicht gültig C99 da Funktionszeiger nicht in 'void *' umgewandelt werden können – Christoph

+0

Die meisten Implementierungen erlauben es schließlich, dass jeder C Compiler rückwärtskompatibel zu den Tagen ist, an denen 'char *' nicht nur a war systemweiter Alias, aber es war der Standard-gesegnete Alias.Aber technisch gesehen hat @Christoph recht, und die konforme Art, dieses Programm zu schreiben, besteht darin, einen Zeiger auf einen anderen Funktionstyp zu deklarieren und ihn zu werfen. Dennoch, mein Beispiel bestanden gcc -Wall, weil es eine nichtkonforme aber bestehende Konvention ist ... – DigitalRoss

0

Wenn eine Funktion sich selbst als Argument nehmen könnte, könnte sie anonyme Rekursion durchführen, indem sie sich selbst aufruft. Aber Rekursion ist unmöglich im einfach typisierten Lambda-Kalkül (das ist im Grunde, was Sie hier haben, mit Funktionstypen).Sie müssen einen Fixpunktkombinator implementieren, der rekursive Funktionen oder rekursive Typen verwendet, um eine anonyme Rekursion durchzuführen.

+0

Können Sie mir bitte eine Idee geben, wo Sie weitere Informationen finden können? Grob gesagt für Informationen, die ich bin auf der Suche, die ich würde es ermöglichen, zu verstehen: 1) Was einfach typisiert wird Lambda-Kalkül 2) Dass dies isomorph es 3) Dass es Rekursion Dank ausschließt! – drxzcl

+1

Danke für ein paar nützliche Schlüsselwörter! Ich wette, meine nächste Frage lautet: "Mit welchen Büchern kann ich FP Theorie studieren?" –

+1

Was hat ein simplifiziertes Lambda-Kalkül mit C/C++ zu tun? –

2

Ein verwandtes Problem gibt einen Funktionszeiger des gleichen Typs zurück. Es kommt bei der Implementierung von State Machines auf, so dass es seine eigene entry in the C FAQ bekam.

Die gleichen Problemumgehungen können auf Ihr Problem angewendet werden.

1

Ich glaube nicht, dass Sie eine Funktion haben können, die sich als ein Argument in C++ ohne irgendeine Art von Trickery nehmen kann, wie Ihre Funktion in eine Klasse zu bringen und die Funktion diese Klasse als Argument zu nehmen.

Eine andere Möglichkeit, die möglich ist, sobald der neue C++ - Standard auftrifft, ist die Verwendung von Lambdas und die Erfassung des Lambda selbst. Ich denke, es würde so etwas gehen:

auto recursive_lambda = [&recursive_lambda] { recursive_lambda(); }; 

Seien Sie gewarnt, dass eine solche Aussage in jedem Compiler, der lambdas unterstützt, völlig ungetestet ist. Ihre Laufleistung kann variieren.

5

Ein weiterer schmutziger Trick.

void Foo(...) 
{ 
} 

int main() 
{ 
Foo(Foo); 
} 

Das obige Programm kompiliert ohne Fehler. Aber es ist nicht rekursiv. Folgende modifizierte Funktion ist rekursive Version mit einem Begrenzer.

#define RECURSIVE_DEPTH (5) 

typedef void (*FooType)(int, ...); 

void Foo(int Depth, ...) 
{ 
void (*This)(int, ...); 

va_list Arguments; 

va_start(Arguments, Depth); 

if(Depth) 
{ 
    This = va_arg(Arguments, FooType); 

    This(Depth - 1, This); 
} 

va_end (Arguments); 
} 

int main() 
{ 
Foo(RECURSIVE_DEPTH, Foo); 
} 
+1

Die oben genannten Programme rufen undefiniertes Verhalten mit void main() auf. main() sollte immer als Rückgabe eines int in C und C++ deklariert werden. –

+0

@David - danke für den Tipp. – Vadakkumpadath

+0

Was spricht dagegen, den Funktionszeiger mit normalen 'va_arg' /' va_start'-Aufrufen zu empfangen, ohne diese verfluchten Offset-Berechnungen? Sie scheinen unnötig zu sein und machen den gesamten Code nicht tragbar. Hätte aufgefrischt, wenn diese Offset-Berechnungen nicht da wären :) –

Verwandte Themen