¿Por qué no puedo definir una función dentro de otra función?

Esta no es una pregunta de función lambda, sé que puedo asignar una lambda a una variable.

¿Cuál es el punto de permitirnos declarar, pero no definir una función dentro del código?

Por ejemplo:

#include  int main() { // This is illegal // int one(int bar) { return 13 + bar; } // This is legal, but why would I want this? int two(int bar); // This gets the job done but man it's complicated class three{ int m_iBar; public: three(int bar):m_iBar(13 + bar){} operator int(){return m_iBar;} }; std::cout << three(42) << '\n'; return 0; } 

Entonces, lo que quiero saber es por qué C ++ permitiría two que parece inútil, y three que parece mucho más complicado, pero no one permite.

EDITAR:

De las respuestas parece que la statement dentro del código puede evitar la contaminación del espacio de nombres, lo que esperaba oír es la razón por la cual se ha permitido la capacidad de declarar funciones, pero se ha desautorizado la capacidad de definir funciones.

No es obvio por qué one no está permitido; las funciones anidadas se propusieron hace mucho tiempo en N0295 que dice:

Discutimos la introducción de funciones anidadas en C ++. Las funciones anidadas se entienden bien y su introducción requiere poco esfuerzo por parte de los proveedores de comstackdores, progtwigdores o el comité. Las funciones anidadas ofrecen ventajas significativas, […]

Obviamente, esta propuesta fue rechazada, pero dado que no tenemos las minutas disponibles en línea para el año 1993 , no tenemos una posible fuente para la justificación de este rechazo.

De hecho, esta propuesta se observa en expresiones Lambda y cierres para C ++ como una posible alternativa:

Un artículo [Bre88] y la propuesta N0295 para el comité C ++ [SH93] sugieren agregar funciones anidadas a C ++. Las funciones anidadas son similares a las expresiones lambda, pero se definen como sentencias dentro de un cuerpo de función, y el cierre resultante no se puede usar a menos que esa función esté activa. Estas propuestas tampoco incluyen agregar un nuevo tipo para cada expresión lambda, sino implementarlas más como funciones normales, lo que incluye permitir un tipo especial de puntero a la función para referirse a ellas. Ambas propuestas son anteriores a la adición de plantillas a C ++, por lo que no mencionan el uso de funciones anidadas en combinación con algoritmos generics. Además, estas propuestas no tienen forma de copiar las variables locales en un cierre, por lo que las funciones anidadas que producen son completamente inutilizables fuera de su función envolvente.

Teniendo en cuenta que ahora tenemos lambdas es poco probable que veamos funciones anidadas ya que, como se describe en el documento, son alternativas para el mismo problema y las funciones anidadas tienen varias limitaciones relativas a lambdas.

En cuanto a esta parte de tu pregunta:

 // This is legal, but why would I want this? int two(int bar); 

Hay casos en que esto sería una forma útil de llamar a la función que desea. El borrador de la sección estándar de C ++ 3.4.1 [basic.lookup.unqual] nos brinda un ejemplo interesante:

 namespace NS { class T { }; void f(T); void g(T, int); } NS::T parm; void g(NS::T, float); int main() { f(parm); // OK: calls NS::f extern void g(NS::T, float); g(parm, 1); // OK: calls g(NS::T, float) } 

Bueno, la respuesta es “razones históricas”. En C, podría tener declaraciones de funciones en el ámbito del bloque, y los diseñadores de C ++ no vieron el beneficio al eliminar esa opción.

Un ejemplo de uso sería:

 #include  int main() { int func(); func(); } int func() { std::cout << "Hello\n"; } 

OMI esta es una mala idea porque es fácil cometer un error al proporcionar una statement que no coincide con la definición real de la función, lo que lleva a un comportamiento indefinido que no será diagnosticado por el comstackdor.

En el ejemplo que das, void two(int) se declara como una función externa, y esa statement solo es válida dentro del scope de la función main .

Eso es razonable si solo desea que el nombre two esté disponible dentro de main() para evitar contaminar el espacio de nombres global dentro de la unidad de comstackción actual.

Ejemplo en respuesta a los comentarios:

main.cpp:

 int main() { int foo(); return foo(); } 

foo.cpp:

 int foo() { return 0; } 

no hay necesidad de archivos de encabezado. comstackr y vincular con

 c++ main.cpp foo.cpp 

se comstackrá y se ejecutará, y el progtwig devolverá 0 como se esperaba.

Puedes hacer estas cosas, en gran parte porque en realidad no son tan difíciles de hacer.

Desde el punto de vista del comstackdor, tener una statement de función dentro de otra función es bastante trivial de implementar. El comstackdor necesita un mecanismo para permitir que las declaraciones dentro de las funciones manejen otras declaraciones (por ejemplo, int x; ) dentro de una función de todos modos.

Por lo general, tendrá un mecanismo general para analizar una statement. Para el tipo que escribe el comstackdor, realmente no importa en absoluto si ese mecanismo se invoca al analizar el código dentro o fuera de otra función; es solo una statement, por lo que cuando ve lo suficiente para saber que lo que hay allí es una statement, invoca la parte del comstackdor que trata las declaraciones.

De hecho, prohibir estas declaraciones particulares dentro de una función probablemente agregaría complejidad adicional, porque el comstackdor necesitaría entonces una verificación completamente gratuita para ver si ya está buscando el código dentro de una definición de función y en función de eso decidir si permite o prohibe este particular statement.

Eso deja la pregunta de cómo una función anidada es diferente. Una función anidada es diferente debido a cómo afecta la generación de código. En los lenguajes que permiten funciones anidadas (por ejemplo, Pascal) normalmente espera que el código en la función anidada tenga acceso directo a las variables de la función en la que está nested. Por ejemplo:

 int foo() { int x; int bar() { x = 1; // Should assign to the `x` defined in `foo`. } } 

Sin funciones locales, el código para acceder a las variables locales es bastante simple. En una implementación típica, cuando la ejecución ingresa a la función, se asigna un bloque de espacio para las variables locales en la stack. Todas las variables locales se asignan en ese bloque único, y cada variable se trata simplemente como un desplazamiento desde el principio (o final) del bloque. Por ejemplo, consideremos una función como esta:

 int f() { int x; int y; x = 1; y = x; return y; } 

Un comstackdor (asumiendo que no optimizó el código extra) podría generar código para esto más o menos equivalente a esto:

 stack_pointer -= 2 * sizeof(int); // allocate space for local variables x_offset = 0; y_offset = sizeof(int); stack_pointer[x_offset] = 1; // x = 1; stack_pointer[y_offset] = stack_pointer[x_offset]; // y = x; return_location = stack_pointer[y_offset]; // return y; stack_pointer += 2 * sizeof(int); 

En particular, tiene una ubicación que apunta al comienzo del bloque de variables locales, y todo el acceso a las variables locales es como compensaciones desde esa ubicación.

Con funciones anidadas, ya no es el caso; en cambio, una función tiene acceso no solo a sus propias variables locales, sino a las variables locales para todas las funciones en las que está nested. En lugar de tener solo un “stack_pointer” desde el que calcula un desplazamiento, necesita retroceder en la stack para encontrar los stack_pointers locales a las funciones en las que está nested.

Ahora bien, en un caso trivial eso tampoco es tan terrible: si la bar está anidada dentro de foo , entonces bar solo puede buscar la stack en el puntero de la stack anterior para acceder a las variables de foo . ¿Derecha?

¡Incorrecto! Bueno, hay casos en que esto puede ser cierto, pero no es necesariamente el caso. En particular, la bar podría ser recursiva, en cuyo caso una invocación dada de la bar podría tener que buscar una cantidad casi arbitraria de niveles de respaldo en la stack para encontrar las variables de la función circundante. En general, debe hacer una de estas dos cosas: o coloca datos adicionales en la stack, de modo que puede realizar una copia de seguridad de la stack en tiempo de ejecución para encontrar el marco de stack de la función circundante, o bien pasa un puntero a el marco de stack de la función circundante como un parámetro oculto para la función anidada. Ah, pero tampoco necesariamente hay una sola función circundante: si puedes anidar funciones, probablemente puedas anidarlas (más o menos) de forma arbitraria, por lo que debes estar preparado para pasar una cantidad arbitraria de parámetros ocultos. Eso significa que normalmente terminas con algo así como una lista vinculada de marcos de stack a funciones circundantes, y el acceso a las variables de las funciones circundantes se realiza recorriendo esa lista vinculada para encontrar su puntero de stack y luego accediendo a un desplazamiento desde ese puntero de stack.

Eso, sin embargo, significa que el acceso a una variable “local” puede no ser un asunto trivial. Encontrar el marco de stack correcto para acceder a la variable puede ser no trivial, por lo que el acceso a las variables de las funciones circundantes también es (al menos usualmente) más lento que el acceso a variables verdaderamente locales. Y, por supuesto, el comstackdor debe generar código para encontrar los marcos de stack correctos, las variables de acceso a través de cualquiera de un número arbitrario de marcos de stack, y así sucesivamente.

Esta es la complejidad que C estaba evitando al prohibir funciones anidadas. Ahora bien, es cierto que un comstackdor de C ++ actual es un tipo de bestia bastante diferente del comstackdor de C vintage de los años setenta. Con cosas como la herencia virtual múltiple, un comstackdor de C ++ tiene que lidiar con cosas de la misma naturaleza general en cualquier caso (es decir, encontrar la ubicación de una variable de clase base en tales casos puede ser no trivial también). Sobre una base porcentual, las funciones anidadas de apoyo no agregarían mucha complejidad a un comstackdor actual de C ++ (y algunas, como gcc, ya las admiten).

Al mismo tiempo, rara vez agrega mucha utilidad tampoco. En particular, si quiere definir algo que actúa como una función dentro de una función, puede usar una expresión lambda. Lo que esto realmente crea es un objeto (es decir, una instancia de alguna clase) que sobrecarga el operador de llamada de función ( operator() ) pero aún así le da capacidades similares a funciones. Sin embargo, hace que la captura (o no) de datos del contexto circundante sea más explícita, lo que le permite utilizar mecanismos existentes en lugar de inventar un mecanismo completamente nuevo y un conjunto de reglas para su uso.

En pocas palabras: aunque inicialmente parezca que las declaraciones anidadas son difíciles y las funciones anidadas son triviales, más o menos es lo contrario: las funciones anidadas son en realidad mucho más complejas de respaldar que las declaraciones anidadas.

El primero es una definición de función, y no está permitido. Obviamente, wt es el uso de poner una definición de una función dentro de otra función.

Pero los otros dos son solo declaraciones. Imagine que necesita utilizar int two(int bar); función dentro del método principal. Pero se define debajo de la función main() , por lo que la statement de función dentro de la función te hace usar esa función con declaraciones.

Lo mismo aplica para el tercero. Las declaraciones de clase dentro de la función le permiten usar una clase dentro de la función sin proporcionar un encabezado o referencia apropiados.

 int main() { // This is legal, but why would I want this? int two(int bar); //Call two int x = two(7); class three { int m_iBar; public: three(int bar):m_iBar(13 + bar) {} operator int() {return m_iBar;} }; //Use class three *threeObj = new three(); return 0; } 

Esta característica del lenguaje fue heredada de C, donde sirvió para algún propósito en los primeros días de C (¿el scope de las declaraciones de funciones podría ser?) . No sé si esta característica es utilizada por los progtwigdores de C modernos y sinceramente lo dudo.

Entonces, para resumir la respuesta:

no hay ningún propósito para esta característica en C ++ moderno (que yo sepa, al menos), está aquí debido a la compatibilidad con versiones anteriores de C ++ a C (supongo :)).


Gracias al comentario a continuación:

El prototipo de función tiene el scope de la función en la que está declarado, por lo que uno puede tener un espacio de nombres global más ordenado, al referirse a funciones externas / símbolos sin #include .

En realidad, hay un caso de uso que es concebiblemente útil. Si quiere asegurarse de que se llame a cierta función (y su código comstack), no importa lo que declare el código circundante, puede abrir su propio bloque y declarar el prototipo de la función en él. (La inspiración es originalmente de Johannes Schaub, https://stackoverflow.com/a/929902/3150802 , a través de TeKa, https://stackoverflow.com/a/8821992/3150802 ).

Esto puede ser especialmente útil si tiene que incluir encabezados que no controla, o si tiene una macro de líneas múltiples que puede usarse en un código desconocido.

La clave es que una statement local reemplaza las declaraciones previas en el bloque interno más interno. Si bien eso puede introducir errores sutiles (y, creo, está prohibido en C #), se puede usar conscientemente. Considerar:

 // somebody's header void f(); // your code { int i; int f(); // your different f()! i = f(); // ... } 

La vinculación puede ser interesante porque es probable que los encabezados pertenezcan a una biblioteca, pero supongo que puede ajustar los argumentos del enlazador para que f() se resuelva en su función para cuando se considere la biblioteca. O le dices que ignore los símbolos duplicados. O no enlaza con la biblioteca.

Esta no es una respuesta a la pregunta OP, sino una respuesta a varios comentarios.

No estoy de acuerdo con estos puntos en los comentarios y las respuestas: 1 que las declaraciones anidadas son supuestamente inofensivas, y 2 que las definiciones anidadas son inútiles.

1 El contraejemplo principal para la supuesta inofensividad de las declaraciones de funciones anidadas es el más infame Parse Vez . IMO la propagación de la confusión causada por él es suficiente para garantizar una regla adicional que prohíbe las declaraciones anidadas.

2 El primer contraejemplo para la presunta inutilidad de las definiciones de funciones anidadas es la necesidad frecuente de realizar la misma operación en varios lugares dentro de una función exactamente. Hay una solución obvia para esto:

 private: inline void bar(int abc) { // Do the repeating operation } public: void foo() { int a, b, c; bar(a); bar(b); bar(c); } 

Sin embargo, esta solución a menudo contamina la definición de la clase con numerosas funciones privadas, cada una de las cuales se usa exactamente en una persona que llama. Una statement de función anidada sería mucho más limpia.

Específicamente respondiendo esta pregunta:

De las respuestas parece que la statement dentro del código puede evitar la contaminación del espacio de nombres, lo que esperaba oír es la razón por la cual se ha permitido la capacidad de declarar funciones, pero se ha desautorizado la capacidad de definir funciones.

Porque considera este código:

 int main() { int foo() { // Do something return 0; } return 0; } 

Preguntas para los diseñadores de idiomas:

  1. ¿Debería foo() estar disponible para otras funciones?
  2. Si es así, ¿cuál debería ser su nombre? int main(void)::foo() ?
  3. (Tenga en cuenta que 2 no sería posible en C, el creador de C ++)
  4. Si queremos una función local, ya tenemos una forma: convertirla en un miembro estático de una clase definida localmente. Entonces, ¿deberíamos agregar otro método sintáctico para lograr el mismo resultado? ¿Por qué hacer eso? ¿No boostía la carga de mantenimiento de los desarrolladores de comstackdores de C ++?
  5. Y así…

Solo quería señalar que el comstackdor GCC le permite declarar funciones dentro de funciones. Lea más sobre esto aquí . También con la introducción de lambdas a C ++, esta pregunta es un poco obsoleta ahora.


La capacidad de declarar encabezados de función dentro de otras funciones, me pareció útil en el siguiente caso:

 void do_something(int&); int main() { int my_number = 10 * 10 * 10; do_something(my_number); return 0; } void do_something(int& num) { void do_something_helper(int&); // declare helper here do_something_helper(num); // Do something else } void do_something_helper(int& num) { num += std::abs(num - 1337); } 

¿Qué tenemos aquí? Básicamente, tienes una función que se supone que se llama desde main, entonces lo que haces es que reenvíes declararlo como normal. Pero luego te das cuenta, esta función también necesita otra función para ayudarlo con lo que está haciendo. Entonces, en lugar de declarar esa función de ayuda por encima de main, la declara dentro de la función que la necesita y luego puede invocarse desde esa función y esa función solamente.

Mi punto es que declarar encabezados de funciones dentro de las funciones puede ser un método indirecto de encapsulación de funciones, que permite a una función ocultar algunas partes de lo que está haciendo delegando en alguna otra función de la que solo es consciente, casi dando la ilusión de una jerarquía función .

Las declaraciones de función anidadas están permitidas probablemente para 1. Reenviar referencias 2. Para poder declarar un puntero a función (es) y pasar alrededor de otra función (es) en un scope limitado.

Las definiciones de funciones anidadas no están permitidas, probablemente debido a problemas como 1. Optimización 2. Recursividad (funciones delimitadoras y funciones definidas anidadas) 3. Reentrada 4. Concurrencia y otros problemas de acceso de múltiples hilos.

De mi comprensión limitada 🙂