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?
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_);
}
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 */
}
std::abs
functionNote 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.