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

I expected that the output will be -5and 5, but the output is the -5 and -5.

I wonder why this case will happen?

Does it have anything to do with the use of std or what?

Lii
  • 11,553
  • 8
  • 64
  • 88
Peter
  • 743
  • 5
  • 6

2 Answers2

93

The language specification allows implementations to implement <cmath> by declaring (and defining) the standard functions in global namespace and then bringing them into namespace std by means of using-declarations. It is unspecified whether this approach is used

20.5.1.2 Headers
4 [...] In the C++ standard library, however, the declarations (except for names which are defined as macros in C) are within namespace scope (6.3.6) of the namespace std. It is unspecified whether these names (including any overloads added in Clauses 21 through 33 and Annex D) are first declared within the global namespace scope and are then injected into namespace std by explicit using-declarations (10.3.3).

Apparently, you are dealing with one of implementations that decided to follow this approach (e.g. GCC). I.e. your implementation provides ::abs, while std::abs simply "refers" to ::abs.

One question that remains in this case is why in addition to the standard ::abs you were able to declare your own ::abs, i.e. why there's no multiple definition error. This might be caused by another feature provided by some implementations (e.g. GCC): they declare standard functions as so called weak symbols, thus allowing you to "replace" them with your own definitions.

These two factors together create the effect you observe: weak-symbol replacement of ::abs also results in replacement of std::abs. How well this agrees with the language standard is a different story... In any case, don't rely on this behavior - it is not guaranteed by the language.

In GCC this behavior can be reproduced by the following minimalistic example. One source file

#include <iostream>

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

Another source file

#include <iostream>

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

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

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

In this case you will also observe that the new definition of ::foo ("Goodbye!") in the second source file also affects the behavior of N::foo. Both calls will output "Goodbye!". And if you remove the definition of ::foo from the second source file, both calls will dispatch to the "original" definition of ::foo and output "Hello!".


The permission given by the above 20.5.1.2/4 is there to simplify implementation of <cmath>. Implementations are allowed to simply include C-style <math.h>, then redeclare the functions in std and add some C++-specific additions and tweaks. If the above explanation properly describes the inner mechanics of the issue, then a major part of it depends on replaceability of weak symbols for C-style versions of the functions.

Note that if we simply globally replace int with double in the above program, the code (under GCC) will behave "as expected" - it will output -5 5. This happens because C standard library does not have abs(double) function. By declaring our own abs(double), we do not replace anything.

But if after switching from int with double we also switch from abs to fabs, the original weird behavior will reappear in its full glory (output -5 -5).

This is consistent with the above explanation.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • as I can see in source of cmath there is no `using ::abs;` like for the `using ::asin;` so You can override the declaration, another point to mention is that the defined in std namespace functions are not declared for int but rather for *double*, *float* – Take_Care_ Jun 17 '18 at 16:58
  • 2
    From the view of the standard, the behavior is undefined per [\[extern.names\]/4](http://www.eel.is/c++draft/extern.names#4). – xskxzr Jun 17 '18 at 17:29
  • But when I deleted the `#include` in my code, I got the same answer.` – Peter Jun 17 '18 at 17:36
  • @Peter But then where are you getting std::abs from? -- It might be being included via another include, at which point you're back to this explanation. (It doesn't matter to the compiler if a header is included directly or indirectly.) – R.M. Jun 17 '18 at 18:00
  • @Peter: `abs` can be declared in `` as well, which might be implicitly included through ``. Try removing your own `abs` and seeing if it still compiles. – AnT stands with Russia Jun 17 '18 at 18:30
  • It doesn't matter where you put the `using ::foo`: the "Goodbye!" definition is a redeclaration (and definition) of the same (overload of) `::foo` selected by the `using`. – Davis Herring Jun 18 '18 at 13:11
13

Your code causes undefined behaviour.

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

Each function signature from the C standard library declared with external linkage is reserved to the implementation for use as a function signature with both extern "C" and extern "C++" linkage, or as a name of namespace scope in the global namespace.

So you cannot make a function with the same prototype as the Standard C library function int abs(int);. Regardless of which headers you actually include or whether those headers also put C library names into the global namespace.

However, it would be allowed to overload abs if you provide different parameter types.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    "or as a name of namespace scope in the global namespace", so it cannot be overloaded in global namespace. – xskxzr Jun 18 '18 at 06:31
  • @xskxzr I am not sure about the interpretation of the text you quote; if it is taken to mean that the user cannot declare anything of that name in the global namespace then the previous part of the text I quoted would be redundant, as would most of [extern.names]/3. Which leads me to think that something else was intended here. – M.M Jun 19 '18 at 09:01