¿Puede una declaración afectar el espacio de nombres estándar?

96
#include <iostream>
#include <cmath>

/* Intentionally incorrect abs() which seems to override std::abs() */
int abs(int a) {
    return a > 0? -a : a;
}

int main() {
    int a = abs(-5);
    int b = std::abs(-5);
    std::cout<< a << std::endl << b << std::endl;
    return 0;
}

Esperaba que la salida fuera -5y 5, pero la salida es -5y -5.

Me pregunto por qué sucederá este caso.

¿Tiene algo que ver con el uso de stdo qué?

Pedro
fuente
1
Su implementación de abses incorrecta.
Richard Critten
31
@RichardCritten Ese es el punto. OP pregunta por qué agregar estos absefectos rotos std::abs().
HolyBlackCat
11
Interesante, me pongo 5y 5con clang, -5y -5con gcc.
Rakete1111
10
Cmake no es un compilador, sino un sistema de construcción. Puede usar cmake para compilar con varios compiladores.
HolyBlackCat
5
Probablemente le hubiera recomendado que simplemente tuviera su función return 0; eso hubiera evitado que la gente pensara que implementó la función de manera incorrecta involuntariamente y habría aclarado el comportamiento deseado y real.
Bernhard Barker

Respuestas:

92

La especificación del lenguaje permite que las implementaciones se implementen <cmath>declarando (y definiendo) las funciones estándar en el espacio de nombres global y luego llevándolas al espacio stdde nombres por medio de declaraciones de uso. No se especifica si se utiliza este enfoque.

20.5.1.2 Encabezados
4 [...] En la biblioteca estándar de C ++, sin embargo, las declaraciones (excepto los nombres que se definen como macros en C) están dentro del alcance del espacio de nombres (6.3.6) del espacio de nombres std. No se especifica si estos nombres (incluidas las sobrecargas agregadas en las Cláusulas 21 a 33 y el Anexo D) se declaran primero dentro del alcance del espacio de nombres global y luego se inyectan en el espacio stdde nombres mediante declaraciones de uso explícitas (10.3.3).

Aparentemente, se trata de una de las implementaciones que decidió seguir este enfoque (por ejemplo, GCC). Es decir, su implementación proporciona ::abs, mientras que std::abssimplemente "se refiere" a ::abs.

Una pregunta que permanece en este caso es por qué, además del estándar ::abs, pudo declarar el suyo ::abs, es decir, por qué no hay error de definición múltiple. Esto podría deberse a otra característica proporcionada por algunas implementaciones (por ejemplo, GCC): declaran funciones estándar como los llamados símbolos débiles , lo que le permite "reemplazarlos" con sus propias definiciones.

Estos dos factores juntos crean el efecto que observa: el reemplazo de símbolo débil de ::abstambién da como resultado el reemplazo destd::abs . Lo bien que esto concuerda con el estándar del lenguaje es una historia diferente ... En cualquier caso, no confíe en este comportamiento, no está garantizado por el lenguaje.

En GCC, este comportamiento se puede reproducir con el siguiente ejemplo minimalista. Un archivo fuente

#include <iostream>

void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }

Otro archivo fuente

#include <iostream>

void foo();
namespace N { using ::foo; }

void foo() { std::cout << "Goodbye!" << std::endl; }

int main()
{
  foo();
  N::foo();
}

En este caso, también observará que la nueva definición de ::foo( "Goodbye!") en el segundo archivo fuente también afecta el comportamiento de N::foo. Ambas llamadas saldrán "Goodbye!". Y si elimina la definición de ::foodel segundo archivo fuente, ambas llamadas se enviarán a la definición ::fooy salida "original" "Hello!".


El permiso otorgado por el 20.5.1.2/4 anterior está ahí para simplificar la implementación de <cmath>. Se permite que las implementaciones incluyan simplemente el estilo C <math.h>, luego redeclaren las funciones stdy agreguen algunas adiciones y ajustes específicos de C ++. Si la explicación anterior describe correctamente la mecánica interna del problema, entonces una gran parte depende de la posibilidad de reemplazar los símbolos débiles por las versiones de estilo C de las funciones.

Tenga en cuenta que si simplemente reemplazamos globalmente intcon doubleen el programa anterior, el código (en GCC) se comportará "como se esperaba": se generará -5 5. Esto sucede porque la biblioteca estándar de C no tiene abs(double)función. Al declarar nuestroabs(double) , no reemplazamos nada.

Pero si después de cambiar de intcon doubletambién cambiamos de absa fabs, el comportamiento extraño original reaparecerá en todo su esplendor (salida-5 -5 ).

Esto es consistente con la explicación anterior.

Hormiga
fuente
como puedo ver en la fuente de cmath, no hay nada using ::abs;parecido para el, por using ::asin;lo que puede anular la declaración, otro punto a mencionar es que las funciones de espacio de nombres definidas en std no se declaran para int sino para double , float
Take_Care_
2
Desde el punto de vista del estándar, el comportamiento no está definido por [extern.names] / 4 .
xskxzr
Pero cuando borré el #include<cmath>en mi código, obtuve la misma respuesta
Peter
@Peter Pero entonces, ¿de dónde sacas std :: abs? - Es posible que se incluya a través de otra inclusión, momento en el que vuelve a esta explicación. (No le importa al compilador si un encabezado se incluye directa o indirectamente)
RM
@Peter: también absse puede declarar <cstdlib>, que podría incluirse implícitamente mediante <iostream>. Intente eliminar el suyo absy ver si aún se compila.
2018
13

Su código provoca un comportamiento indefinido.

C ++ 17 [nombres.extern] / 4:

Cada firma de función de la biblioteca estándar C declarada con enlace externo está reservada para la implementación para su uso como firma de función con enlace externo "C" y externo "C ++", o como un nombre de ámbito de espacio de nombres en el espacio de nombres global.

Por lo tanto, no puede crear una función con el mismo prototipo que la función de la biblioteca C estándar int abs(int);. Independientemente de qué encabezados incluya realmente o si esos encabezados también colocan los nombres de las bibliotecas de C en el espacio de nombres global.

Sin embargo, podría sobrecargarse abssi proporciona diferentes tipos de parámetros.

MM
fuente
1
"o como un nombre de ámbito de espacio de nombres en el espacio de nombres global", por lo que no se puede sobrecargar en el espacio de nombres global.
xskxzr
@xskxzr No estoy seguro de la interpretación del texto que cita; si se entiende que el usuario no puede declarar nada de ese nombre en el espacio de nombres global, entonces la parte anterior del texto que cité sería redundante, al igual que la mayoría de [extern.names] / 3. Lo que me lleva a pensar que aquí se pretendía otra cosa.
MM