c++templatesgccx86int128

Cannot overload std::abs() for GCC 128 bit integer


I'm getting compiler errors when I call abs() with GCC's 128 bit type. My line looks like:

__extension__ using int128_t = __int128;

int128_t mantissa_;

// Other code

using namespace std;
int128_t temp = abs(mantissa_);

The error I'm getting locally is:

 error: call of overloaded ‘abs(const int128_t&)’ is ambiguous
     int128_t temp = abs(mantissa_);
/usr/include/stdlib.h:840:12: note: candidate: ‘int abs(int)’
  840 | extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur;

/usr/include/c++/11/bits/std_abs.h:56:3: note: candidate: ‘long int std::abs(long int)’
   56 |   abs(long __i) { return __builtin_labs(__i); }
      |   ^~~
/usr/include/c++/11/bits/std_abs.h:61:3: note: candidate: ‘long long int std::abs(long long int)’
   61 |   abs(long long __x) { return __builtin_llabs (__x); }
      |   ^~~
/usr/include/c++/11/bits/std_abs.h:71:3: note: candidate: ‘constexpr double std::abs(double)’
   71 |   abs(double __x)
      |   ^~~
/usr/include/c++/11/bits/std_abs.h:75:3: note: candidate: ‘constexpr float std::abs(float)’
   75 |   abs(float __x)
      |   ^~~
/usr/include/c++/11/bits/std_abs.h:79:3: note: candidate: ‘constexpr long double std::abs(long double)’
   79 |   abs(long double __x)

so it's not considering my overload (below) as a candidate?

namespace std 
{
    __extension__ using int128_t = __int128;
    
    int128_t abs(int128_t x)
    {
        return x < 0 ? x * -1 : x;  // Not ideal but no builtin for 128
    }
}

Is the overload correct?

However, when I mock an example in Godbolt, even without overloading abs(), it compiles fine:

https://godbolt.org/z/P8T1fGxcK

#include <iostream>

__extension__ using int128_t = __int128;

struct Decimal
{
    Decimal(int128_t q) : mantissa_(q)
    {
        volatile int128_t x = abs(mantissa_);  // How is this compiling?
        std::cout << "ctor" << std::endl;
    }

    int128_t mantissa_;
};

int main()
{
    Decimal dec(-6);
}

Are Godbolt using a library like Abseil, they're providing a function and that's why it is compiling?


Solution

  • 1. How to get std::abs working for __int128

    The following only applies for gcc version >= 7.

    For __int128 gcc provides an overload of std::abs with the following code snippet:

    libstdc++-v3/include/bits/std_abs.h

    #if defined(__GLIBCXX_TYPE_INT_N_0)
      __extension__ inline _GLIBCXX_CONSTEXPR __GLIBCXX_TYPE_INT_N_0
      abs(__GLIBCXX_TYPE_INT_N_0 __x) { return __x >= 0 ? __x : -__x; }
    #endif
    

    (or one of the following 3 similar functions using __GLIBCXX_TYPE_INT_N_1, __GLIBCXX_TYPE_INT_N_2, etc...)

    To get those macros defined you need to compile with gnu extensions enabled (i.e. not strict c++)
    For this you need to build with -std=gnu++20 instead of -std=c++20 (or -std=gnu++17, -std=gnu++14, etc... depending on the c++ version you want to target)
    (if no -std= option is given at all then gcc will default to std=gnu++xx (c++ version depending on compiler version))

    When compiling with gnu extensions gcc will define those macros automatically for __int128 (assuming gcc supports 128-bit ints for your target platform):
    godbolt

    #define __GLIBCXX_BITSIZE_INT_N_0 128
    #define __GLIBCXX_TYPE_INT_N_0 __int128
    

    Unfortunately the c abs function-family (and the gcc builtin for it - __builtin_abs) do not support __int128 at all, and calling them would result in truncation of the result value.

    Example: (compiled with -std=gnu++20 -Wconversion):
    godbolt

    #include <cmath>
    
    __extension__ using int128_t = __int128;
    
    int128_t mantissa_;
    
    int main() {
        {
            using namespace std;
            // OK - calls std::abs(__int128)
            int128_t foo = abs(mantissa_);
        }
    
        // OK - calls std::abs(__int128)
        int128_t bar = std::abs(mantissa_);
    
        // WARNING: calls abs(int) --> truncation
        int128_t baz = abs(mantissa_);
    
        // WARNING: calls abs(int) --> truncation
        int128_t foobar = __builtin_abs(mantissa_);
    }
    

    2. Why the godbolt compiles

    The godbolt you provided compiles due to it calling the c function int abs(int)
    (the code does not include using namespace std;, so std::abs is not visible)

    The c abs-family of functions have different names for the possible types, so a call to abs() with __int128 as argument will not be ambigous (but will result in truncation to int):

    int        abs  ( int n );
    long       labs ( long n );
    long long  llabs( long long n );
    
    /* floating-point versions would be fabs, ... */
    

    The c++-variant of abs - std::abs - is implemented with overloads instead, so a call to std::abs with __int128 will be ambigous, assuming there is no overload for __int128 defined. (__int128 -> long long, __int128 -> long, __int128 -> int, etc... would be Integral conversions; __int128 -> double, __int128 -> float, etc.. would be Floating-integral conversions. Both Integral conversions and Floating-integral conversions and have the same rank (Conversion), so none of those overloads would be a better match than any other -> ambigous)

    namespace std {
        int       abs( int n );
        long      abs( long n );
        long long abs( long long n );
    
        /* more overloads of abs for floating-point types */
    }
    

    3. Manually adding the std::abs function

    Note that adding declarations to std is generally undefined behaviour (with a few exceptions), so i would not recommend this.

    Here's a comparison of the gcc version vs your version:

    // version provided by gcc (when compiling with gnu extensions)
    // (macros substituted for better readability)
    namespace std {
        __extension__ inline constexpr __int128 abs(__int128 __x) {
            return __x >= 0 ? __x : -__x;
        }
    }
    
    // your version
    namespace std {
        __extension__ using int128_t = __int128;
        
        int128_t abs(int128_t x)
        {
            return x < 0 ? x * -1 : x;  // Not ideal but no builtin for 128
        }
    }
    

    They're functionally identical, the ternary condition is just inverted and you're multiplying by negative one whereas gcc uses the unary minus operator.

    godbolt example