2009-10-03 13 views
11

Al leer una pregunta en stackoverflow, me pregunté si es posible declarar una función que toma un puntero a sí mismo. Es decir. para hacer tal declaración de foo, para los cuales los siguientes sería correcta:¿Puedo declarar una función que puede tomar el puntero a sí mismo como argumento?

foo(foo); 

La idea más simple es emitir a otro puntero de función (no pueden transmitir contenido a void*, ya que puede ser más pequeño), por lo que la declaración de la función se parece a esto:

void foo(void (*)()); 

Mientras eso está bien en C (y trabajará con el bastidor en C++), me pregunto, si puede hacerse sin dicha información "reinterpretar" casting o perder tipo duro.

En otras palabras, quiero que la siguiente declaración:

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

pero, por supuesto, las declaraciones son ilimitadas imposible. Naive typedef ing tampoco ayuda.

Las plantillas de C++ son bienvenidas, incluso si hacen que el código de llamada (foo(foo)) parezca un poco menos conciso, pero igual finito.

Las respuestas de tipo C que muestran cómo se puede soltar información de tipo sin transmitir, u otros trucos de ese tipo son, por supuesto, interesantes, pero no serán aceptados.

+1

¿Existe un lenguaje funcional en el que esto sea posible? – Crashworks

+0

Existen idiomas no funcionales en los que es posible, p. C# –

+0

es trivial en cualquier lenguaje de tipado dinámico, p. Python, Ruby, Perl, PHP, JavaScript, solo por nombrar algunos ... – newacct

Respuesta

2

En general, estoy de acuerdo con Darío - hacer esto en el tipo de nivel parece imposible.

Pero puede utilizar clases ("patrón de estrategia"):

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

Usted puede incluso agregar operador():

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

Generalmente este tipo de cosas se hacen mejor en los idiomas de PF. versión Haskell es simplemente:

data Evil = Evil (Evil -> Integer) 

Este utiliza un envoltorio (Evil) que corresponde a la utilización de una clase.

Del interrogador: lo que le faltaba a esta respuesta era la capacidad de pasar varias funciones diferentes como argumento de una de ellas. Esto puede resolverse haciendo evil() virtual o almacenando explícitamente en el objeto un puntero de función que lo implementa (que es básicamente el mismo).

Con esta aclaración, la respuesta es lo suficientemente buena para ser aceptada.

+0

Me pegó a eso ... – DigitalRoss

+0

Buena idea. Pero también me gustaría pasar más parámetros junto con el puntero a la función. Veo una forma de hacerlo con el uso del patrón de fábrica para clonar "funciones" y pasar los parámetros como argumentos de constructor. Pero esto parece demasiado complicado ... –

+0

¿A dónde pasa exactamente esos parámetros? Mi comprensión ingenua sería operador nulo() (A a, int x, int y) - puede considerar A como el tipo de funciones que toman A y dos ints y vuelven vacías. La versión de Haskell sería datos Evil = Evil (Evil -> Integer -> Integer -> T) donde T es el tipo de retorno. ¿Te entendí? El patrón de fábrica es currying en FP, es mucho más claro en ML/Haskell, pero no entiendo por qué es necesario aquí. De ser posible, amplíe la pregunta con algún ejemplo de uso de esos parámetros adicionales (no necesariamente uno del mundo real). – sdcvvc

5

Aparentemente no - vea this thread. El tipo requerido aquí siempre sería infinito.

+1

Todavía no veo una relación entre "tipo infinito" y "posible". Si pensara que hay uno, no haría la pregunta, ¿o sí? –

+1

Todos los idiomas de tipo estático que conozco requieren que sus tipos sean finitos. Si un tipo es infinito, el sistema de tipos no puede expresarlo. Pero adelante, invente un sistema de tipo infinito. – Dario

+0

¿No se pueden declarar los tipos infinitos ...? Usted mismo lo dijo, "las declaraciones ilimitadas son imposibles". – GManNickG

0

Este es un uso perfectamente válido de void *.

typedef void T(void *); 

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

Ah, un buen ejemplo de lo que llamé "truco estilo C". ;-) –

+6

en realidad, eso no es válido C99 ya que los punteros a las funciones no se pueden convertir a 'void *' – Christoph

+0

La mayoría de las implementaciones lo permiten, después de todo, cada compilador de C es retrocompatible con los días en que 'char *' no era solo alias de todo el sistema, pero era el alias bendito estándar.Pero técnicamente, @Christoph tiene razón, y la forma correcta de escribir este programa es declarar un puntero a otro tipo de función y emitirlo. Aún así, mi ejemplo pasó gcc -Wall porque es una convención no conforme pero existente ... – DigitalRoss

0

Si una función podría tomarse como argumento, entonces podría realizar una recursión anónima llamándose a sí misma. Pero la recursión es imposible en el cálculo lambda simplemente tipado (que es básicamente lo que tienes aquí, con tipos de funciones).Debe implementar un combinador de punto fijo utilizando funciones recursivas o tipos recursivos para realizar recursiones anónimas.

+0

¿Podría darme una idea de dónde encontrar información adicional? En términos generales estoy buscando información que me permita entender: 1) Lo que se escribe simplemente lambda-cálculo 2) Que esto es isomorfo a ella 3) que excluye la recursividad Gracias! – drxzcl

+1

¡Gracias por un montón de palabras clave útiles! Apuesto a que mi siguiente pregunta será "¿Con qué libros puedo estudiar teoría de FP?" –

+1

¿Qué tiene que ver el cálculo lambda simplemente tipado con C/C++? –

2

Un problema relacionado es devolver un puntero de función del mismo tipo. Aparece al implementar máquinas de estado, por lo que obtuvo su propio entry in the C FAQ.

Se pueden aplicar las mismas soluciones a su problema.

1

No creo que pueda tener una función que pueda tomarse como argumento en C++ sin algún tipo de engaño, como poner su función en una clase, y hacer que la función tome esa clase como argumento.

Otra forma en que será posible una vez que los nuevos estándares de C++ coincidan es usar lambdas, y hacer que la captura lambda misma. Creo que sería más o menos así:

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

ser advertidos de que tal declaración es totalmente no probado en cualquier compilador que apoya lambdas. Su experiencia puede ser diferente.

5

Otro truco sucio.

void Foo(...) 
{ 
} 

int main() 
{ 
Foo(Foo); 
} 

El programa anterior se compilará sin ningún error. Pero no es recursivo. La siguiente función modificada es la versión recursiva con un limitador.

#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

Los programas anteriores invocan un comportamiento indefinido con void main(). main() siempre debe declararse como devolver un int en C y C++. –

+0

@David - gracias por la sugerencia. – Vadakkumpadath

+0

¿Qué hay en contra de recibir el puntero de función usando llamadas 'va_arg' /' va_start' normales, sin esos cálculos de compensación malditos? Parecen ser innecesarios y hacen que todo el código no sea portátil. Habría dado la vuelta si esos cálculos de compensación no estaban allí :) –

Cuestiones relacionadas