Can a declaration affect the std namespace?
Asked Answered
G

2

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?

Greyhen answered 17/6, 2018 at 16:31 Comment(11)
Your implementation of abs is incorrect.Nucleolus
@RichardCritten That's the point. OP's asking why adding this broken abs affects std::abs().Rosenkrantz
I've seen this before, and IIRC, declaring an unnamespaced abs like this is not allowedCabral
Interesting, I get 5 and 5 with clang, -5 and -5 with gcc.Lipoid
@Lipoid and in cmake is also -5and-5.Greyhen
Cmake is not a compiler, but rather a build system. You can use cmake to build with various compilers.Rosenkrantz
I get -5 and -5 with Visual C++ 14 on rextester.comSeclude
Datapoint: MSVC gives -5 and -5.Heer
I'd probably have recommend you simply have your function return 0 - that would've avoided having people think you unintentionally implemented the function incorrectly and made the desired and actual behaviour clearer.Decennium
@Lipoid Sounds like the clang behaviour would make a good follow-up question - it's not exactly expected either, is it?Inviting
@Inviting At least not to me just before :)Lipoid
O
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.

Ordeal answered 17/6, 2018 at 16:40 Comment(6)
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, floatUnciform
From the view of the standard, the behavior is undefined per [extern.names]/4.Ensign
But when I deleted the #include<cmath> in my code, I got the same answer.`Greyhen
@Greyhen 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.)Weaver
@Peter: abs can be declared in <cstdlib> as well, which might be implicitly included through <iostream>. Try removing your own abs and seeing if it still compiles.Ordeal
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.Izzard
B
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.

Bristow answered 18/6, 2018 at 1:35 Comment(2)
"or as a name of namespace scope in the global namespace", so it cannot be overloaded in global namespace.Ensign
@Ensign 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.Bristow

© 2022 - 2024 — McMap. All rights reserved.