Tested x86_64-linux. Pushed to trunk. -- >8 --
This makes it possible to format _Float32, _Float64 etc. in C++20 mode. Previously it was only possible to format them in C++23 when the <stdfloat> typedefs and the std::to_chars overloads were defined. Instead of relying on std::to_chars for those types, we can just reuse the formatters for float, double and long double. This also avoids template bloat by reusing the same specializations instead of instantiating __formatter_fp for every different type. libstdc++-v3/ChangeLog: * include/std/format (formatter): Add partial specializations for extended floating-point types. * testsuite/std/format/functions/format.cc: Move test_float128() to ... * testsuite/std/format/formatter/ext_float.cc: New test. --- libstdc++-v3/include/std/format | 146 +++++++++++++++++- .../std/format/formatter/ext_float.cc | 92 +++++++++++ .../testsuite/std/format/functions/format.cc | 33 ---- 3 files changed, 236 insertions(+), 35 deletions(-) create mode 100644 libstdc++-v3/testsuite/std/format/formatter/ext_float.cc diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index 79f810acce3..13f700a10bf 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -2033,6 +2033,7 @@ namespace __format }; #endif +#if defined __cpp_lib_to_chars /// Format a floating-point value. template<__format::__formattable_float _Tp, __format::__char _CharT> struct formatter<_Tp, _CharT> @@ -2053,6 +2054,140 @@ namespace __format __format::__formatter_fp<_CharT> _M_f; }; +#if __LDBL_MANT_DIG__ == __DBL_MANT_DIG__ + // Reuse __formatter_fp<C>::format<double, Out> for long double. + template<__format::__char _CharT> + struct formatter<long double, _CharT> + { + formatter() = default; + + [[__gnu__::__always_inline__]] + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { return _M_f.parse(__pc); } + + template<typename _Out> + typename basic_format_context<_Out, _CharT>::iterator + format(long double __u, basic_format_context<_Out, _CharT>& __fc) const + { return _M_f.format((double)__u, __fc); } + + private: + __format::__formatter_fp<_CharT> _M_f; + }; +#endif + +#if defined(__FLT16_DIG__) + // Reuse __formatter_fp<C>::format<float, Out> for _Float16. + template<__format::__char _CharT> + struct formatter<_Float16, _CharT> + { + formatter() = default; + + [[__gnu__::__always_inline__]] + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { return _M_f.parse(__pc); } + + template<typename _Out> + typename basic_format_context<_Out, _CharT>::iterator + format(_Float16 __u, basic_format_context<_Out, _CharT>& __fc) const + { return _M_f.format((float)__u, __fc); } + + private: + __format::__formatter_fp<_CharT> _M_f; + }; +#endif + +#if defined(__FLT32_DIG__) + // Reuse __formatter_fp<C>::format<float, Out> for _Float32. + template<__format::__char _CharT> + struct formatter<_Float32, _CharT> + { + formatter() = default; + + [[__gnu__::__always_inline__]] + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { return _M_f.parse(__pc); } + + template<typename _Out> + typename basic_format_context<_Out, _CharT>::iterator + format(_Float32 __u, basic_format_context<_Out, _CharT>& __fc) const + { return _M_f.format((float)__u, __fc); } + + private: + __format::__formatter_fp<_CharT> _M_f; + }; +#endif + +#if defined(__FLT64_DIG__) + // Reuse __formatter_fp<C>::format<double, Out> for _Float64. + template<__format::__char _CharT> + struct formatter<_Float64, _CharT> + { + formatter() = default; + + [[__gnu__::__always_inline__]] + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { return _M_f.parse(__pc); } + + template<typename _Out> + typename basic_format_context<_Out, _CharT>::iterator + format(_Float64 __u, basic_format_context<_Out, _CharT>& __fc) const + { return _M_f.format((double)__u, __fc); } + + private: + __format::__formatter_fp<_CharT> _M_f; + }; +#endif + +#if defined(__FLT128_DIG__) && _GLIBCXX_FORMAT_F128 == 1 + // Reuse __formatter_fp<C>::format<__float128_t, Out> for _Float128. + template<__format::__char _CharT> + struct formatter<_Float128, _CharT> + { + formatter() = default; + + [[__gnu__::__always_inline__]] + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { return _M_f.parse(__pc); } + + template<typename _Out> + typename basic_format_context<_Out, _CharT>::iterator + format(_Float128 __u, basic_format_context<_Out, _CharT>& __fc) const + { return _M_f.format((__format::__float128_t)__u, __fc); } + + private: + __format::__formatter_fp<_CharT> _M_f; + }; +#endif + +#if defined(__BFLT16_DIG__) + // Reuse __formatter_fp<C>::format<float, Out> for bfloat16_t. + template<__format::__char _CharT> + struct formatter<__gnu_cxx::__bfloat16_t, _CharT> + { + formatter() = default; + + [[__gnu__::__always_inline__]] + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { return _M_f.parse(__pc); } + + template<typename _Out> + typename basic_format_context<_Out, _CharT>::iterator + format(__gnu_cxx::__bfloat16_t __u, + basic_format_context<_Out, _CharT>& __fc) const + { return _M_f.format((float)__u, __fc); } + + private: + __format::__formatter_fp<_CharT> _M_f; + }; +#endif +#endif // __cpp_lib_to_chars + /** Format a pointer. * @{ */ @@ -2702,7 +2837,6 @@ namespace __format __int128 _M_i128; unsigned __int128 _M_u128; #endif - // TODO _Float16 etc. #ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT __ieee128 _M_f128; __ibm128 _M_ibm128; @@ -2931,7 +3065,15 @@ namespace __format return type_identity<__ieee128>(); #endif - // TODO bfloat16 and float16 +#if defined(__FLT16_DIG__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) + else if constexpr (is_same_v<_Td, _Float16>) + return type_identity<float>(); +#endif + +#if defined(__BFLT16_DIG__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) + else if constexpr (is_same_v<_Td, decltype(0.0bf16)>) + return type_identity<float>(); +#endif #ifdef __FLT32_DIG__ else if constexpr (is_same_v<_Td, _Float32>) diff --git a/libstdc++-v3/testsuite/std/format/formatter/ext_float.cc b/libstdc++-v3/testsuite/std/format/formatter/ext_float.cc new file mode 100644 index 00000000000..89810295b64 --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/formatter/ext_float.cc @@ -0,0 +1,92 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include <format> +#include <testsuite_hooks.h> + +template<typename T> +bool format_float() +{ + auto s = std::format("{:#} != {:<+7.3f}", (T)-0.0, (T)0.5); + return s == "-0. != +0.500 "; +} + +#if __cplusplus > 202002L +template<typename T> +concept formattable = std::formattable<T, char>; +#else +template<typename T> +concept formattable = std::semiregular<std::formatter<T, char>>; +#endif + +void +test_float16() +{ +#if __FLT16_DIG__ + if constexpr (formattable<_Float16>) + VERIFY( format_float<_Float16>() ); + else + std::puts("Cannot format _Float16 on this target"); +#endif +} + +void +test_float32() +{ +#if __FLT32_DIG__ + if constexpr (formattable<_Float32>) + VERIFY( format_float<_Float32>() ); + else + std::puts("Cannot format _Float32 on this target"); +#endif +} + +void +test_float64() +{ +#if __FLT64_DIG__ + if constexpr (formattable<_Float64>) + VERIFY( format_float<_Float64>() ); + else + std::puts("Cannot format _Float64 on this target"); +#endif +} + +void +test_float128() +{ +#ifdef __SIZEOF_FLOAT128__ + if constexpr (formattable<__float128>) + VERIFY( format_float<__float128>() ); + else + std::puts("Cannot format __float128 on this target"); +#endif +#if __FLT128_DIG__ + if constexpr (formattable<_Float128>) + VERIFY( format_float<_Float128>() ); + else + std::puts("Cannot format _Float128 on this target"); +#endif +} + +void +test_bfloat16() +{ +#if __BFLT16_DIG__ + using bfloat16_t = decltype(0.0bf16); + + if constexpr (formattable<bfloat16_t>) + VERIFY( format_float<bfloat16_t>() ); + else + std::puts("Cannot format bfloat16_t on this target"); +#endif +} + +int main() +{ + test_float16(); + test_float32(); + test_float64(); + test_float128(); + test_bfloat16(); +} diff --git a/libstdc++-v3/testsuite/std/format/functions/format.cc b/libstdc++-v3/testsuite/std/format/functions/format.cc index 59d327fccee..5141cbd11bf 100644 --- a/libstdc++-v3/testsuite/std/format/functions/format.cc +++ b/libstdc++-v3/testsuite/std/format/functions/format.cc @@ -347,38 +347,6 @@ test_p1652r1() // printf corner cases in std::format VERIFY( s == "3.31" ); } -template<typename T> -bool format_float() -{ - auto s = std::format("{:#} != {:<+7.3f}", (T)-0.0, (T)0.5); - return s == "-0. != +0.500 "; -} - -#if __cplusplus > 202002L -template<typename T> -concept formattable = std::formattable<T, char>; -#else -template<typename T> -concept formattable = requires (T t, char* p) { std::to_chars(p, p, t); }; -#endif - -void -test_float128() -{ -#ifdef __SIZEOF_FLOAT128__ - if constexpr (formattable<__float128>) - VERIFY( format_float<__float128>() ); - else - std::puts("Cannot format __float128 on this target"); -#endif -#if __FLT128_DIG__ - if constexpr (formattable<_Float128>) - VERIFY( format_float<_Float128>() ); - else - std::puts("Cannot format _Float128 on this target"); -#endif -} - void test_pointer() { @@ -429,6 +397,5 @@ int main() test_wchar(); test_minmax(); test_p1652r1(); - test_float128(); test_pointer(); } -- 2.41.0