Uma declaração pode afetar o namespace std?

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;
}

Eu esperava que a saída fosse -5e 5, mas a saída é -5e -5.

Eu me pergunto por que esse caso vai acontecer?

Tem alguma coisa a ver com o uso de stdou o quê?

Peter
fonte
1
Sua implementação de absestá incorreta.
Richard Critten
31
@RichardCritten Esse é o ponto. Pedindo do OP por adicionar esta quebrado absafeta std::abs().
HolyBlackCat
11
Interessante, eu entendo 5e 5com clang -5e -5com gcc.
Rakete1111
10
Cmake não é um compilador, mas sim um sistema de construção. Você pode usar o cmake para compilar com vários compiladores.
HolyBlackCat
5
Eu provavelmente recomendaria que você simplesmente tivesse sua função return 0- isso teria evitado que as pessoas pensassem que você implementou a função incorretamente sem querer e tornado o comportamento desejado e real mais claro.
Bernhard Barker

Respostas:

92

A especificação da linguagem permite que as implementações sejam implementadas <cmath>declarando (e definindo) as funções padrão no namespace global e, em seguida, trazendo-as para o namespace stdpor meio de declarações de uso. Não é especificado se esta abordagem é usada

20.5.1.2 Cabeçalhos
4 Na biblioteca padrão C ++, entretanto, as declarações (exceto para nomes que são definidos como macros em C) estão dentro do escopo do namespace (6.3.6) do namespace std. Não é especificado se esses nomes (incluindo quaisquer sobrecargas adicionadas nas Cláusulas 21 a 33 e no Anexo D) são primeiro declarados no escopo do namespace global e, em seguida, injetados no namespace stdpor declarações de uso explícitas (10.3.3).

Aparentemente, você está lidando com uma das implementações que decidiu seguir essa abordagem (por exemplo, GCC). Ou seja, sua implementação fornece ::abs, enquanto std::abssimplesmente "se refere" a ::abs.

Uma questão que permanece neste caso é por que, além do padrão, ::absvocê foi capaz de declarar o seu próprio ::abs, ou seja, por que não há erro de definição múltipla. Isso pode ser causado por outro recurso fornecido por algumas implementações (por exemplo, GCC): eles declaram funções padrão como os chamados símbolos fracos , permitindo assim que você "substitua" por suas próprias definições.

Esses dois fatores juntos criam o efeito que você observa: a substituição do símbolo fraco de ::abstambém resulta na substituição de std::abs. O quão bem isso está de acordo com o padrão da linguagem é uma história diferente ... Em qualquer caso, não confie neste comportamento - não é garantido pela linguagem.

No GCC, esse comportamento pode ser reproduzido pelo seguinte exemplo minimalista. Um arquivo de origem

#include <iostream>

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

Outro arquivo fonte

#include <iostream>

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

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

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

Nesse caso, você também observará que a nova definição de ::foo( "Goodbye!") no segundo arquivo de origem também afeta o comportamento de N::foo. Ambas as chamadas serão geradas "Goodbye!". E se você remover a definição de ::foodo segundo arquivo de origem, ambas as chamadas serão enviadas para a definição "original" de ::fooe para a saída "Hello!".


A permissão dada pelo 20.5.1.2/4 acima existe para simplificar a implementação do <cmath>. As implementações podem simplesmente incluir o estilo C <math.h>, depois declarar novamente as funções stde adicionar algumas adições e ajustes específicos de C ++. Se a explicação acima descreve adequadamente a mecânica interna do problema, a maior parte dela depende da capacidade de substituição de símbolos fracos para versões de estilo C das funções.

Observe que se nós simplesmente substituirmos globalmente intpor doubleno programa acima, o código (sob GCC) se comportará "como esperado" - ele será impresso -5 5. Isso acontece porque a biblioteca padrão C não tem abs(double)função. Ao declarar o nosso abs(double), não substituímos nada.

Mas se depois de alternar de intcom doubletambém mudarmos de abspara fabs, o comportamento estranho original reaparecerá em toda sua glória (saída -5 -5).

Isso é consistente com a explicação acima.

Formiga
fonte
como posso ver no código-fonte do cmath, não há using ::abs;como para o, using ::asin;portanto, você pode sobrescrever a declaração, outro ponto a ser mencionado é que as funções definidas no namespace std não são declaradas para int, mas sim para double , float
Take_Care_
2
Do ponto de vista do padrão, o comportamento é indefinido por [extern.names] / 4 .
xskxzr
Mas quando apaguei o #include<cmath>do meu código, obtive a mesma resposta. »
Peter
@Peter Mas então de onde você está tirando std :: abs? - Pode estar sendo incluído por meio de outro include, momento em que você está de volta a esta explicação. (Não importa para o compilador se um cabeçalho é incluído direta ou indiretamente.)
RM
@Peter: também abspode ser declarado em <cstdlib>, o que pode ser incluído implicitamente por meio de <iostream>. Tente remover o seu próprio abse ver se ele ainda compila.
Antes de
13

Seu código causa comportamento indefinido.

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

Cada assinatura de função da biblioteca padrão C declarada com ligação externa é reservada para a implementação para uso como uma assinatura de função com ligação externa "C" e externa "C ++", ou como um nome de escopo de namespace no namespace global.

Portanto, você não pode fazer uma função com o mesmo protótipo da biblioteca C padrão int abs(int);. Independentemente de quais cabeçalhos você realmente inclui ou se esses cabeçalhos também colocam nomes de biblioteca C no namespace global.

No entanto, seria permitido sobrecarregar absse você fornecer diferentes tipos de parâmetros.

MILÍMETROS
fonte
1
"ou como um nome de escopo de namespace no namespace global", portanto, não pode ser sobrecarregado no namespace global.
xskxzr
@xskxzr Não tenho certeza sobre a interpretação do texto que você cita; se isso significar que o usuário não pode declarar nada com aquele nome no namespace global, a parte anterior do texto que citei seria redundante, como seria a maioria de [extern.names] / 3. O que me leva a pensar que algo mais se pretendia aqui.
MM de