Pitfalls in Using a Standard “Global” C++ Function: Its C Counterpart May Be Camouflaging

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) with x 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 to double type, feed it into double sqrt(double), cast the returned value back to double 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.

Leave a Reply

Your email address will not be published.