c++ - __int128_t 模棱两可的 std::ostream 重载(从命名空间调用时)

标签 c++

我在一个使用大量 __int128_t 类型变量的 C++ 项目上工作。和 __uint128_t (后文 int128uint128 表示敬意)。为了让“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/

相关文章:

c++ - 如何在 Qt 中创建旋转立方体效果?

c++ - 如何防止 Visual C++ 创建 stdafx.h?

c++ - 应用程序的缓存友好设计

c++ - boost::filesystem::last_write_time 在哪里?

尝试编译 gstreamer 应用程序时 #include <anyfile.h> 的 C++ 问题

c++ - Visual C++ 中的 Mixin 类问题

c++ - 项目中的函数、类定义和主要代码应该如何分离?

c++ - XKB - 获取大写锁定掩码

c++ - 是否必须安装sqlite或者是否可以在代码中导入

c++ - 如何禁止在 C++ 2011 中调用右值对象的 const 成员函数?