2009-10-03 9 views
11

Leggendo una domanda in StackOverflow, mi chiedevo se è possibile dichiarare una funzione che accetta un puntatore a se stesso. Cioè per rendere tale dichiarazione di foo, per cui i seguenti sarebbe corretto:Posso dichiarare una funzione che può assumere il puntatore come argomento?

foo(foo); 

L'idea più semplice è colata a un'altra puntatore di funzione (non possono trasmettere void*, poiché può essere più piccola), quindi la dichiarazione di funzione assomiglia a questo:

void foo(void (*)()); 

Mentre va bene in C (e funziona con getto in C++), mi chiedo, se può essere fatto senza così dura informazione "reinterpretare" casting o perdere tipo.

In altre parole, voglio la seguente dichiarazione:

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

ma, ovviamente, le dichiarazioni illimitate sono impossibili. Naive typedef non aiuta neanche.

I modelli C++ sono ben accetti, anche se rendono il codice di chiamata (foo(foo)) un po 'meno sintetico, ma comunque finito.

Le risposte in stile C che mostrano come si possono eliminare informazioni di tipo senza eseguire il casting o altri trucchi del genere sono, ovviamente, interessanti, ma non saranno accettate.

+1

Esiste un linguaggio funzionale in cui ciò è possibile? – Crashworks

+0

Esistono lingue non funzionali in cui è possibile, ad es. C# –

+0

è banale da fare in qualsiasi linguaggio tipizzato dinamicamente, ad es. Python, Ruby, Perl, PHP, JavaScript, solo per citarne alcuni ... – newacct

risposta

2

Generalmente sono d'accordo con Dario: rendere questo a livello di tipo sembra impossibile.

Ma è possibile utilizzare le classi ("modello di strategia"):

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

È anche possibile aggiungere operator():

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

Generalmente tali cose sono fatte meglio in lingue FP. Versione Haskell è semplicemente:

data Evil = Evil (Evil -> Integer) 

Questo utilizza un wrapper (Evil) che corrisponde all'utilizzo di una classe.

Dall'interrogante: ciò che mancava a questa risposta era la possibilità di passare diverse funzioni come argomento di una di esse. Questo può essere risolto rendendo evil() virtuale o memorizzando esplicitamente nell'oggetto un puntatore a funzione che lo implementa (che è fondamentalmente lo stesso).

Con questo chiarimento, la risposta è sufficiente per essere accettata.

+0

Mi picchia per ... – DigitalRoss

+0

Bella idea. Ma mi piacerebbe anche passare più parametri insieme al puntatore di funzione. Vedo un modo di farlo con l'uso del pattern factory per clonare "funzioni" e passare i parametri come argomenti del costruttore. Ma questo sembra troppo complicato ... –

+0

Dove stai passando esattamente questi parametri? La mia ingenua comprensione sarebbe void operator() (A a, int x, int y) - puoi considerare A come il tipo di funzioni che accettano A e due inte e restituiscono vuoto. La versione di Haskell sarebbe data Evil = Evil (Evil -> Integer -> Integer -> T) dove T è il tipo di ritorno. Ti ho capito? Il modello di fabbrica è in FP: è molto più chiaro in ML/Haskell, ma non capisco perché sia ​​necessario qui. Se possibile, estendi la domanda con qualche esempio di utilizzo di questi parametri aggiuntivi (non necessariamente uno di quelli reali). – sdcvvc

5

Apparentemente no - vedere this thread. Il tipo richiesto qui sarebbe sempre infinito.

+1

Non vedo ancora una relazione tra "tipo infinito" e "possibile". Se pensassi che ce ne sia uno, non farei la domanda, vero? –

+1

Tutte le lingue tipizzate staticamente che conosco richiedono che i loro tipi siano finiti. Se un tipo è infinito, il sistema dei tipi non è in grado di esprimere questo. Ma vai avanti, inventare un sistema di tipo infinito. – Dario

+0

Non è possibile dichiarare tipi infiniti ...? L'hai detto tu stesso, "le dichiarazioni illimitate sono impossibili". – GManNickG

0

Questo è un uso perfettamente valido di void *.

typedef void T(void *); 

void f(T *probably_me) 
{ 
    (*probably_me)(f); 
} 
+0

Ah, un buon esempio di quello che ho chiamato "trucco in stile C". ;-) –

+6

in realtà, non è valido C99 poiché i puntatori di funzione non possono essere trasmessi a 'void *' – Christoph

+0

La maggior parte delle implementazioni lo consente, dopo tutto, ogni compilatore C è retrocompatibile con i giorni in cui 'char *' non era solo un alias di sistema, ma era l'alias benedetto.Ma tecnicamente, @Christoph ha ragione, e il modo conforme per scrivere questo programma è dichiarare un puntatore a un altro tipo di funzione e lanciarlo. Comunque, il mio esempio ha passato gcc -Wall perché è una convenzione non conforme ma esistente ... – DigitalRoss

0

Se una funzione può assumere se stessa come argomento, può eseguire la ricorsione anonima chiamando se stessa. Ma la ricorsione è impossibile nel calcolo lambda semplicemente tipizzato (che è sostanzialmente quello che hai qui, con i tipi di funzione).È necessario implementare un combinatore a virgola fissa utilizzando funzioni ricorsive o tipi ricorsivi per eseguire la ricorsione anonima.

+0

Puoi per favore darmi un'idea di dove trovare ulteriori informazioni? In parole povere Sto cercando informazioni che mi avrebbe permesso di capire: 1) Qual è semplicemente-digitato lambda calcolo 2) Che questo è isomorfo ad esso 3) che esclude la ricorsione Grazie! – drxzcl

+1

Grazie per un sacco di utili parole chiave! Scommetto che la mia prossima domanda sarà "Con quali libri posso studiare la teoria della FP?" –

+1

Che cosa ha a che fare il calcolo lambda semplicemente tipizzato con C/C++? –

2

Un problema correlato restituisce un puntatore di funzione dello stesso tipo. Viene fuori quando si implementano macchine a stati, quindi ha il suo entry in the C FAQ.

La stessa soluzione alternativa può essere applicata al problema.

1

Non credo che si possa avere una funzione che può assumere se stessa come argomento in C++ senza alcun tipo di inganno, come mettere la propria funzione in una classe e avere la funzione che prende quella classe come argomento.

Un altro modo che sarà possibile una volta raggiunto il nuovo standard C++ consiste nell'utilizzare lambda e fare in modo che l'acquisizione lambda stessa. Penso che sarebbe più o meno così:

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

essere avvertiti che una tale affermazione è assolutamente non testato in qualsiasi compilatore che supporta lambda. Il tuo chilometraggio può variare.

2

SI

Si tratta di una variante di "Can you write a function that returns a pointer to itself?", salvo che nel tuo caso, il tipo di funzione appare in modo ricorsivo come argumkent, non come il tipo di ritorno. La risposta di Herb Sutters è comunque riutilizzabile: avvolgere il puntatore in una classe proxy dichiarata in avanti.

5

Un altro trucco sporco.

void Foo(...) 
{ 
} 

int main() 
{ 
Foo(Foo); 
} 

Sopra il programma verrà compilato senza errori. Ma non è ricorsivo. La seguente funzione modificata è la versione ricorsiva con un limitatore.

#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

I programmi precedenti invocano un comportamento non definito con void main(). main() dovrebbe sempre essere dichiarato come restituire un int in C e C++. –

+0

@David - grazie per il suggerimento. – Vadakkumpadath

+0

Cosa non va se si riceve il puntatore alla funzione usando normali chiamate 'va_arg' /' va_start', senza i calcoli di offset maledetto? Sembrano non essere necessari e rendono l'intero codice non portatile. Avresti svalutato se quei calcoli di offset non erano lì :) –

Problemi correlati