Const de nivel superior no influye en la firma de una función

De C ++ Primer 5th Edition, dice:

int f(int){ /* can write to parameter */} int f(const int){ /* cannot write to parameter */} 

Las dos funciones son indistinguibles . Pero como usted sabe, las dos funciones realmente difieren en cómo pueden actualizar sus parámetros.

¿Puede alguien explicarme?


EDITAR
Creo que no interpreté bien mi pregunta. Lo que realmente me importa es por qué C ++ no permite estas dos funciones simultáneamente como una función diferente ya que son realmente diferentes en cuanto a “si el parámetro puede escribirse o no”. ¡Intuitivamente, debería ser!


EDITAR
La naturaleza de pasar por valor en realidad pasa por hacer frente a los valores de los argumentos a los valores de los parámetros . Incluso para referencias y punteros donde los valores copiados son direcciones . Desde el punto de vista de la persona que llama, si const o non-const se pasa a la función no influye en los valores (y por supuesto, los tipos de) copiados a los parámetros.
La distinción entre const de nivel superior y const de bajo nivel importa cuando se copian objetos. Más específicamente, const de nivel superior (no es el caso de const de bajo nivel ) se ignora al copiar objetos, ya que la copia no influirá en el objeto copiado. Es irrelevante si el objeto copiado o copiado es const o no.
Entonces, para la persona que llama, no es necesario diferenciarlos. Probablemente, desde el punto de vista de la función, los parámetros de const de nivel superior no influyan en la interfaz y / o la funcionalidad de la función. Las dos funciones realmente logran lo mismo. ¿Por qué molestarse en implementar dos copias?

permita estas dos funciones simultáneamente como funciones diferentes ya que son realmente diferentes en cuanto a “si el parámetro puede escribirse o no”. ¡Intuitivamente, debería ser!

La sobrecarga de funciones se basa en los parámetros que proporciona la persona que llama. Aquí, es cierto que la persona que llama puede proporcionar un valor const o non- const , pero lógicamente no debería hacer ninguna diferencia en la funcionalidad que proporciona la función llamada. Considerar:

 f(3); int x = 1 + 2; f(x); 

Si f() hace cosas diferentes en cada una de estas situaciones, ¡sería muy confuso! El progtwigdor de este código que llama f() puede tener una expectativa razonable de comportamiento idéntico, agregando o eliminando libremente variables que pasan parámetros sin que invalide el progtwig. Este comportamiento seguro y sensato es el punto de partida al que le gustaría justificar las excepciones, y de hecho hay uno: los comportamientos pueden variar cuando el ala sobrecargada de la función:

 void f(const int&) { ... } void f(int&) { ... } 

Por lo tanto, supongo que esto es lo que no es intuitivo: C ++ proporciona más “seguridad” (comportamiento consistente forzado a través del soporte de una sola implementación) para referencias que no sean referencias .

Las razones por las que puedo pensar son:

  • Entonces, cuando un progtwigdor sabe que un parámetro no const& tendrá una vida más larga, puede seleccionar una implementación óptima. Por ejemplo, en el código a continuación, puede ser más rápido devolver una referencia a un miembro T dentro de F , pero si F es temporal (que podría ser si el comstackdor coincide con const F& ) se necesita un retorno de valor por debajo. Esto sigue siendo bastante peligroso ya que la persona que llama tiene que ser consciente de que la referencia devuelta solo es válida siempre que el parámetro esté alrededor.
     T f (const F &);
     T & f (F &);  // el tipo de devolución podría ser por const y si es más apropiado
  • propagación de calificadores como const -ness a través de llamadas a funciones como en:
     const T & f (const F &);
     T & f (F &);

Aquí, alguna variable (presumiblemente miembro F ) de tipo T se expone como const o no const función de la const -ness del parámetro cuando se invoca f() . Este tipo de interfaz se puede elegir cuando se desea extender una clase con funciones no miembro (para mantener la clase minimalista, o al escribir plantillas / algos que se pueden usar en muchas clases), pero la idea es similar a las funciones miembro como vector::operator[]() , donde quiere v[0] = 3 permitido en un vector no const pero no en uno.

Cuando los valores son aceptados por el valor, salen del ámbito a medida que la función retorna, por lo que no existe un escenario válido que implique devolver una referencia a parte del parámetro y querer propagar sus calificadores.

Hackear el comportamiento que quieres

Dadas las reglas para las referencias, puede usarlas para obtener el tipo de comportamiento que desea; solo debe tener cuidado de no modificar el parámetro by-non-const-reference accidentalmente, por lo que es posible que desee adoptar una práctica como la siguiente para los parámetros no const:

 T f(F& x_ref) { F x = x_ref; // or const F is you won't modify it ...use x for safety... } 

Implicaciones de recomstackción

Aparte de la cuestión de por qué el lenguaje prohíbe la sobrecarga en función de la const -ness de un parámetro by-value, surge la pregunta de por qué no insiste en la consistencia de la const -ness en la statement y la definición.

Para f(const int) / f(int) … si está declarando una función en un archivo de encabezado, entonces es mejor NO incluir el calificador const incluso si la última definición en un archivo de implementación lo tendrá. Esto es porque durante el mantenimiento el progtwigdor puede desear eliminar el calificador … quitarlo del encabezado puede desencadenar una recomstackción sin sentido del código del cliente, por lo que es mejor no insistir en que se mantengan sincronizados, y de hecho es por eso que el comstackdor no lo hace t produce un error si difieren. Si simplemente agrega o elimina const en la definición de la función, entonces está cerca de la implementación donde el lector del código podría preocuparse por la constness al analizar el comportamiento de la función. Si lo tiene const tanto en el encabezado como en el archivo de implementación, entonces el progtwigdor desea hacerlo no const y olvida o decide no actualizar el encabezado para evitar la recomstackción del cliente, entonces es más peligroso que al revés, ya que es posible el progtwigdor tendrá en cuenta la versión de const del encabezado al tratar de analizar el código de implementación actual que conduce a un razonamiento incorrecto sobre el comportamiento de la función. Todo esto es un problema de mantenimiento muy sutil, solo relevante para la progtwigción comercial, pero esa es la base de la guía para no usar const en la interfaz. Además, es más conciso omitirlo de la interfaz, que es más agradable para los progtwigdores de clientes que leen su API.

Como no existe diferencia para el que llama, y ​​no hay una manera clara de distinguir entre una llamada a una función con un parámetro de nivel superior const y una sin él, las reglas de idioma ignoran las conste de nivel superior. Esto significa que estos dos

 void foo(const int); void foo(int); 

son tratados como la misma statement. Si tuviera que proporcionar dos implementaciones, obtendría un error de definición múltiple.

Hay una diferencia en una definición de función con const de nivel superior. En uno, puede modificar su copia del parámetro. En el otro, no puedes. Puedes verlo como un detalle de implementación. Para quien llama, no hay diferencia.

 // declarations void foo(int); void bar(int); // definitions void foo(int n) { n++; std::cout << n << std::endl; } void bar(const int n) { n++; // ERROR! std::cout << n << std::endl; } 

Esto es análogo a lo siguiente:

 void foo() { int = 42; n++; std::cout << n << std::endl; } void bar() { const int n = 42; n++; // ERROR! std::cout << n << std::endl; } 

En “The C ++ Programming Language”, cuarta edición, Bjarne Stroustrup escribe (§12.1.3):

Desafortunadamente, para preservar la compatibilidad C, se ignora una const en el nivel más alto de un tipo de argumento. Por ejemplo, estas son dos declaraciones de la misma función:

 void f(int); void f(const int); 

Por lo tanto, parece que, contrariamente a algunas de las otras respuestas, esta regla de C ++ no se eligió debido a la indistinguibilidad de las dos funciones, u otras razones similares, sino como una solución menos que óptima, por el bien de compatibilidad.

De hecho, en el lenguaje de progtwigción D , es posible tener esas dos sobrecargas. Sin embargo, al contrario de lo que otras respuestas a esta pregunta podrían sugerir, la sobrecarga sin const es preferible si la función se llama con un literal:

 void f(int); void f(const int); f(42); // calls void f(int); 

Por supuesto, debe proporcionar una semántica equivalente para sus sobrecargas, pero eso no es específico de este escenario de sobrecarga, con funciones de sobrecarga casi indistinguibles.

Como dicen los comentarios, dentro de la primera función el parámetro podría cambiarse, si hubiera sido nombrado. Es una copia del int. Dentro de la segunda función, cualquier cambio en el parámetro, que sigue siendo una copia de la int de la llamada, dará como resultado un error de comstackción. Const es una promesa de que no cambiarás la variable.

Una función es útil solo desde la perspectiva de quien llama.

Como no existe diferencia para el que llama, no hay diferencia para estas dos funciones.

Creo que lo indistinguible se usa en términos de sobrecarga y comstackdor, no en términos si se pueden distinguir por la persona que llama.

El comstackdor no distingue entre esas dos funciones, sus nombres se destrozan de la misma manera. Eso lleva a la situación cuando el comstackdor trata esas dos declaraciones como redefinición.

Respondiendo esta parte de su pregunta:

Lo que realmente me importa es por qué C ++ no permite estas dos funciones simultáneamente como una función diferente ya que son realmente diferentes en cuanto a “si el parámetro puede escribirse o no”. ¡Intuitivamente, debería ser!

Si lo piensas un poco más, no es para nada intuitivo, de hecho, no tiene mucho sentido. Como todos han dicho, una persona que llama no está influenciada de ninguna manera cuando una función toma su parámetro por valor y tampoco le importa.

Ahora, supongamos por un momento que la resolución de sobrecarga también funcionó en const nivel superior. Dos declaraciones como esta

 int foo(const int); int foo(int); 

declararía dos funciones diferentes. Uno de los problemas sería a qué funciones llamaría esta expresión: foo(42) . Las reglas del lenguaje podrían decir que los literales son const y que la const “sobrecarga” se llamaría en este caso. Pero ese es el menor de un problema. Un progtwigdor sintiéndose lo suficientemente mal podría escribir esto:

 int foo(const int i) { return i*i; } int foo(int i) { return i*2; } 

Ahora tendría dos sobrecargas que parecen equivalentes semánticamente a la persona que llama, pero que hacen cosas completamente diferentes. Ahora eso sería malo. Podremos escribir interfaces que limiten al usuario por la forma en que lo hace, no por lo que ofrece.