I work on a C++ project that uses a lot of variables of type __int128_t
and __uint128_t
(herafter int128
and uint128
for bevity). In order to make "cout debugging" easier, we wrote a std::ostream
overload for int128
and uint128
types, similar to what is shown in this answer (https://stackoverflow.com/a/25115163/753174) except it handles std::hex, std::setw, std::setfill, etc.
It works fine when called from within the same namespace as the ostream overload (e.g. if both are in the global namespace). It also works fine if called within a namespace that does not have any other ostream overloads in it. For example, this compiles fine and works:
#include <string>
#include <iostream>
#include <stdint.h>
using uint128 = __uint128_t;
using int128 = __int128_t;
inline std::ostream &operator<<(std::ostream &out, uint128 val)
{
//Obviously not the real implementation, just here as an example
return out << static_cast<uint64_t>(val);
}
void print_uint128(uint128 val)
{
std::cout << "A uint128: " << val << std::endl;
}
namespace Something
{
void print_uint128(uint128 val)
{
std::cout << "A uint128: " << val << std::endl;
}
}
int main()
{
uint128 foo = 1234;
print_uint128(foo);
Something::print_uint128(foo);
return 0;
}
But if the ostream operator is used within a namespace that has any other std::ostream overload (even something completely unrelated like, any struct, class, enum), I get a compile error like this (tried gcc 8.3 and 10.2, and clang 9.0.1)
Example that causes the error (https://godbolt.org/z/sozrx7):
#include <string>
#include <iostream>
#include <stdint.h>
using uint128 = __uint128_t;
using int128 = __int128_t;
//Doesn't matter what ABC is, an empty struct demonstrates the
//issue, but same happens if ABC is an enum or enum class
struct ABC { };
namespace Something {
std::ostream &operator<<(std::ostream &os, ABC def)
{
//Doesn't matter what this does
return os;
}
}
inline std::ostream &operator<<(std::ostream &out, uint128 val)
{
return out << static_cast<uint64_t>(val);
}
void print_uint128(uint128 val)
{
std::cout << "An int128: " << val << std::endl;
}
namespace Something
{
void print_uint128(uint128 val)
{
std::cout << "An int128: " << val << std::endl;
}
}
int main()
{
uint128 foo = 1234;
print_uint128(foo);
Something::print_uint128(foo);
return 0;
}
Full compiler output of error:
<source>:36:32: error: use of overloaded operator '<<' is ambiguous (with operand types 'basic_ostream<char, std::char_traits<char> >' and 'uint128' (aka 'unsigned __int128'))
std::cout << "An int128: " << val << std::endl;
~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:166:7: note: candidate function
operator<<(long __n)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:170:7: note: candidate function
operator<<(unsigned long __n)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:174:7: note: candidate function
operator<<(bool __n)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:178:7: note: candidate function
operator<<(short __n);
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:181:7: note: candidate function
operator<<(unsigned short __n)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:189:7: note: candidate function
operator<<(int __n);
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:192:7: note: candidate function
operator<<(unsigned int __n)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:201:7: note: candidate function
operator<<(long long __n)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:205:7: note: candidate function
operator<<(unsigned long long __n)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:220:7: note: candidate function
operator<<(double __f)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:224:7: note: candidate function
operator<<(float __f)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:232:7: note: candidate function
operator<<(long double __f)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:517:5: note: candidate function [with _Traits = std::char_traits<char>]
operator<<(basic_ostream<char, _Traits>& __out, char __c)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:511:5: note: candidate function [with _CharT = char, _Traits = std::char_traits<char>]
operator<<(basic_ostream<_CharT, _Traits>& __out, char __c)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:523:5: note: candidate function [with _Traits = std::char_traits<char>]
operator<<(basic_ostream<char, _Traits>& __out, signed char __c)
^
/opt/compiler-explorer/gcc-9.2.0/lib/gcc/x86_64-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ostream:528:5: note: candidate function [with _Traits = std::char_traits<char>]
operator<<(basic_ostream<char, _Traits>& __out, unsigned char __c)
^
1 error generated.
I can get around this by redeclaring my int128 ostream overload in every namespace where I want to use it, or by calling a to_string()
function explicitly instead of relying on ostream overloads. These workarounds are not ideal though. Am I missing something or is this just how it is? As far as I can tell, GCC and clang still lack ostream overloads for these 128-bit types, despite them being available for many years.