Last updated on September 30, 2019
A large number of C++ functions in the standard library are extended from standard C functions, such as qsort()
, memcpy()
, etc. Among these functions, many have overloaded their C counterparts. For example, abs()
in C++ is overloaded for both integral and floating-point types, while it is only defined for int
in C. These functions, however, are often unwittingly misused as global functions and produce unexpected results.
Functions in the C++ standard library are usually put in the namespace std
, including those that are extended from their C counterparts. For example, the C function sqrt()
corresponds to std::sqrt()
in C++. The following function my_sqrt_1
computes the square root of the input parameter x
:
#include <cmath>
float my_sqrt_1(float x) {
return std::sqrt(x);
}
However, from time to time, people miss the std::
prefix, thinking that it is too redundant. Therefore, the above code snippet is often shortened as (using the “global version” of sqrt()
)
#include <cmath>
// no "using namespace std;"
float my_sqrt_2(float x) {
return sqrt(x);
}
While it may seem to be a nice trick, it may not behave as expected. In C++, the behavior of calling a standard function without explicitly using the namespace std
is implementation-dependent: A compiler may interpret the function as either the C++ function or its C counterpart. In the above example, my_sqrt_2
may possibly either
- call
float std::sqrt(float)
withx
as input and return its returned value (this is the case for x64 msvc v19.22 as tested on the C Compiler Explorer, or - cast
x
todouble
type, feed it intodouble sqrt(double)
, cast the returned value back todouble
and return it (this is the case for x86-64 gcc 9.2 and x86-64 clang 9.0.0 on the C Compiler Explorer.
The second case may occur because the compiler has interpreted sqrt()
as the C function sqrt()
, which is only defined for double
. If the newly defined function is repeatedly called, this may cause significant drawback in performance. What is worse, sometimes it can even cause incorrect results. Consider the following function:
#include <cmath>
long double my_sqrt_3(long double x) {
return sqrt(x);
}
If sqrt()
is interpreted as the C sqrt()
function, on a CPU chip that supports long double
type that is more precise than double
, this function is likely to cause precision loss every time it is called, and can even output ridiculous results when x
is sufficiently large. On a recent x86_64 CPU, using gcc 8.3, the following code snippet
#include <iostream>
int main() {
std::cout << my_sqrt_3(1e310L) << ' ' << std::sqrt(1e310L) << std::endl;
return 0;
}
outputs
inf 1e+155
The square root of a finite number is infinity! This is because x
is first implicitly cast to double
in my_sqrt_3
, which is not precise enough to store a number as large as 1e310
and therefore converted to inf
.
Conclusion: Do not use the “global version” of standard C++ functions; always use the one in the std
namespace.