我在一个使用大量 __int128_t
类型变量的 C++ 项目上工作。和 __uint128_t
(后文 int128
和 uint128
表示敬意)。为了让“cout 调试”更容易,我们写了一个 std::ostream
int128
的过载和 uint128
类型,类似于此答案 (https://stackoverflow.com/a/25115163/753174) 中显示的内容,但它处理 std::hex、std::setw、std::setfill 等。
当从与 ostream 重载相同的命名空间中调用时,它可以正常工作(例如,如果两者都在全局命名空间中)。如果在其中没有任何其他 ostream 重载的命名空间中调用,它也可以正常工作。例如,这编译得很好并且可以工作:
#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;
}
但是,如果在具有任何其他 std::ostream 重载(甚至是完全不相关的东西,例如任何结构、类、枚举)的命名空间中使用 ostream 运算符,我会收到这样的编译错误(尝试 gcc 8.3 和 10.2,以及 clang 9.0.1)导致错误的示例( 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;
}
错误的完整编译器输出:<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.
我可以通过在我想使用它的每个命名空间中重新声明我的 int128 ostream 重载,或者通过调用 to_string()
来解决这个问题。显式地运行而不是依赖于 ostream 重载。但是,这些解决方法并不理想。我错过了什么还是这就是它的样子?据我所知,GCC 和 clang 仍然缺乏这些 128 位类型的 ostream 重载,尽管它们已经存在多年。
最佳答案
使用 using
,您可以将名称 ::operator<<
作为 namespace 的一部分,作为 ABC
已经存在的名称。
然后编译器可以在调用现场选择最合适的。
namespace Something
{
using ::operator<<;
void print_uint128(uint128 val)
{
std::cout << "An int128: " << val << std::endl;
}
}
如果没有这种名称 ::operator<<
的人为注入(inject),当此命名空间中存在另一种类型( <<
)的 ABC
时,无需进一步查看(在全局命名空间中)。由于
<<
,参数相关查找 (ADL) 还从 std::
注入(inject) std::ostream
名称。之后,编译器在所有这些可能的
<<
被认为是可访问的之间选择最佳匹配。ABC
的一个不匹配,但其他的(对于整数、实数...)可以用于转换;但哪一个是最好的?这是模棱两可的。
相反,当
<<
为 ABC
不存在时,当前命名空间中不存在 <<
这样的名称,则在上层(全局)命名空间中查找该名称;这里 uint128
存在完美匹配,因此来自 std::
(ADL) 的候选不被视为潜在的更好匹配。这不容易遵循,因为有两个阶段。
首先,查找
<<
名称。这从当前命名空间开始;如果找到它会停在这里,如果没有找到它会在上层命名空间中继续,依此类推,直到全局命名空间。但是 ADL 也会发生,并根据调用站点的参数从其他命名空间注入(inject)
<<
名称(此处为 std::
为 ostream
)。其次,选择所有这些收集到的
<<
之间的最佳匹配。如果没有完美匹配,则可以考虑进行转换,但如果可以进行多次转换,则这是模棱两可的。
试图说明各种情况:
• NO << (for ABC) in current namespace,
<< (for uint128) in global namespace,
1 --> no << in the current namespace then look in the upper namespace,
find << (for uint128) in global namespace
+ many << from std:: via ADL
2 --> the one from the global namespace is a perfect match --> OK!
• << (for ABC) in current namespace,
<< (for uint128) in global namespace,
1 --> find << (for ABC) in _current_ namespace and _STOP_ here
+ many << from std:: via ADL
2 --> no one is a perfect match,
ABC does not match at all,
various integers/reals could match with conversions --> AMBIGUOUS!
• << (for ABC) in current namespace,
<< (for uint128) in global namespace,
using ::operator<< in current namespace
1 --> find << (for ABC _AND_ for uint128) in _current_ namespace and stop here
+ many << from std:: via ADL
2 --> ABC does not match at all,
various integers/reals could match with conversions,
the one for uint128 is the only perfect match --> OK!
• << (for ABC) in current namespace,
<< (for uint128 *) in global namespace,
1 --> find << (for ABC) in _current_ namespace and _STOP_ here
+ many << from std:: via ADL
2 --> ABC does not match at all,
various integers/reals do not match at all
std::operator<< for void * matches --> OK!!!!!!!!!!!!
_A_MATCH_IS_FOUND_BUT_NOT_YOURS_ (void * not uint128 *)!!!
关于c++ - __int128_t 模棱两可的 std::ostream 重载(从命名空间调用时),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66053030/