不能使用运算符<< 和 std::float128_t;如何打印?

Can't use operator<< with std::float128_t; how do I print it?

提问人:Jan Schultke 提问时间:6/18/2023 更新时间:6/18/2023 访问量:716

问:

我有以下代码,它不能使用 x86_64 GCC 13 编译:

#include <iostream>
#include <stdfloat>

int main() {
    std::cout << std::float128_t{1} << '\n';
}

这给了我以下错误:

<source>: In function 'int main()':
<source>:5:15: error: ambiguous overload for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'std::float128_t' {aka '_Float128'})
    5 |     std::cout << std::float128_t{1} << '\n';
      |     ~~~~~~~~~ ^~      ~~~~~~~~~~~~~
      |          |            |
      |          |            std::float128_t {aka _Float128}
      |          std::ostream {aka std::basic_ostream<char>}

列出的不明确重载包括:

  • operator<<(long)
  • operator<<(unsigned long)
  • operator<<(bool)
  • ...

令人惊讶的是,其他浮点类型没有列出。operator<<(float)

我已经检查了 C++23 的编译器支持页面,应该支持此功能:

C++23 特性 论文 libstdc++ libc++ MVSV STL
扩展浮点类型的标准名称和库
支持
P1467R9 13 19.37**

请参阅 C++23 编译器支持页面

我是不是误解了什么?cppreference 是否错误,并且尚未完全支持扩展浮点类型?如何在没有第三方库的情况下打印 std::float128_t

C++ x86-64 IOstream C++23 标准浮点

评论


答:

14赞 Jan Schultke 6/18/2023 #1

operator<<(std::float128_t)是可选的

不能保证存在任何扩展浮点类型的重载。因为事实并非如此,这是很常见的。 在x86_64:operator<<<stdfloat>std::float128_t

  • long double通常是 80 位浮点类型,并且
  • std::float128_t是四精度 IEEE-754 浮点类型。

这意味着1) 的转换排名大于 。 因此,是可选的:std::float128_tlong doubleoperator<<(std::float128_t)

否则,如果扩展浮点型的浮点转换排名小于或等于 long double 的浮点转换排名,则格式转换将像执行以下代码片段一样进行:

bool failed = use_facet<num_put<charT, ostreambuf_iterator<charT, traits>>>(getloc()).put(*this, *this, fill(), static_cast<long double>(val)).failed();

否则,有条件地支持使用实现定义的语义调用运算符函数。

- [ostream.formatted]/[ostream.inserters.arithmetic] §5

不需要 GCC 来支持它,您应该考虑打印扩展浮点类型的替代方法。operator<<

选择

#include <iostream>
#include <stdfloat>
#include <format>

int main() {
    std::cout << std::format("{}", std::float128_t{1}) << '\n';
}

此解决方案目前有效,并保证有效。 扩展浮点类型是使用 实现的,这是支持所有算术类型所必需的。std::formatterstd::to_chars

#include <print>
#include <stdfloat>

int main() {
    std::println("{}", std::float128_t{1});
}

此解决方案尚不起作用,因为 libstdc++ 尚未实现标头。 但是,一旦这样做,这也将起作用,因为还使用 .<print>std::printlnstd::formatter

如果有效,为什么不支持?std::formatoperator<<

提案文件回答了这个问题:

流式处理运算符使用虚函数,用于算术类型的输出和输入。为了完全正确地支持扩展浮点类型,需要添加新的虚拟函数。这将是 ABI 中断。虽然 ABI 突破并非不可能,但它会遭到强烈反对。该提案不值得为使 ABI 突破委员会而付出必要的努力。num_put<>::do_putnum_get<>::do_get

- P1467r9 §iostream


1) 转化排名更大,因为可以表示比 更多的值,参见 [conv.rank] §2std::float128_tlong double

2赞 HolyBlackCat 6/18/2023 #2

老式的解决方案是 libquadmath,它默认带有 GCC。

#include <iostream>
#include <quadmath.h>

int main()
{
    __float128 x = 12.3; // `__float128` should be equivalent to `std::float128_t`.
    char buf[200];
    quadmath_snprintf(buf, sizeof buf, "%Qf", x);
    std::cout << buf << '\n';
}

链接时添加。-lquadmath

评论

2赞 Jan Schultke 6/18/2023
__float128并具有相同的表示形式,但它们是不同的类型。 记录为 convert ,因此在使用它时可能需要强制转换 to 以避免收到编译器警告或更糟的情况。std::float128_t/_Float128quadmath_snprintf__float128std::float128_t__float128
0赞 HolyBlackCat 6/18/2023
@JanSchultke说和是一样的,我没有收到警告.std::is_same_v__float128_Float128-Wall -Wextra
1赞 Jan Schultke 6/18/2023
真?我自己测试过,它不是同一类型:godbolt.org/z/eWYTPnGzrstd::is_same_v
1赞 Jan Schultke 6/19/2023
我不确定您在 GCC 13 之前是如何测试该断言的。对我来说,在 GCC 12 中不存在,所以我无法编译断言。 需要符合 IEEE-754 标准,并且您始终可以保持安全。或者为了更加安全起见,在任何地方明确地在这两种类型之间转换。即使表示相同,族函数中的类型双关语通常也是 UB。_Float128_Float128__float128static_assert(std::numeric_limits<__float128>::is_iec559);printf
1赞 HolyBlackCat 6/19/2023
@JanSchultke啊,正在定义.quadmath.h_Float128