On Wed, Apr 30, 2025 at 1:26 PM Tomasz Kamiński <tkami...@redhat.com> wrote:
> This commits adjust the way how the arguments are stored in the _Arg_value > (and thus basic_format_args), by preserving the types of fixed width > floating-point types, that were previously converted to float, double, > long double. > > The _Arg_value union now contains alternatives with std::bfloat16_t, > std::float16_t, std::float32_t, std::float64_t that use pre-existing > _Arg_bf16, _Arg_f16, _Arg_f32, _Arg_f32 argument types. > > This does not affect formatting, as specialization of formatters for > formats them by casting to the corresponding standard floating point > type. > > For the 128bit floating we need to handle the ppc64 architecture, > (_GLIBCXX_LONG_DOUBLE_ALT128_COMPAT) for which the long double may (per TU > basis) designate either __ibm128 and __ieee128 type, we need to store both > types in the _Arg_value and have two _Arg_types (_Arg_ibm128, > _Arg_ieee128). > On other architectures we use extra enumerator value to store __float128, > that is different from long double and _Float128. This is consistent with > ppc64, > for which __float128 is same type as __ieee128 if present. We use > _Arg_float128 > _M_float128 names that deviate from _Arg_fN naming scheme, to emphasize > that > this flag is not used for std::float128_t (_Float128_t) type, that is > consistenly > formatted via handle. > > The __format::_float128_t type is renamed to __format::__flt128_t, to > mitigate > visual confusion between this type and __float128. We also introduce > __bflt16_t > typedef instead of using of decltype. > > We add new alternative for the _Arg_value and allow them to be accessed > via _S_get, > when the types are available. However, we produce and handle corresponding > _Arg_type, > only when we can format them. See also r14-3329-g27d0cfcb2b33de. > > The formatter<_Float128, _CharT> that formats via __flt128_t is always > provided, when type is available. It is still correct __flt128_t is > _Float128_t. > > We also provide formatter<__float128, _CharT> that formats via __flt128_t. > As this type may be disabled (-mno-float128), extra care needs to be taken, > for situation when __float128 is same as long double. If the formatter > would be > defined in such case, the formatter<long double, charT> would be generated > from > different specializations, and have different mangling: > * formatter<__float128, _CharT> if __float128 is present, > * formatter<_format::__formattable_float, _CharT> otherwise. > To best of my knowledge this happens only on ppc64 for __ieee128 and > __float128, > so the formatter is not defined in this case. static_assert is added to > detect > other configurations like that. In such case we should replace it with > constraint. > > PR libstdc++/119246 > > libstdc++-v3/ChangeLog: > > * include/std/format (__format::__bflt16_t): Define. > (_GLIBCXX_FORMAT_F128): Separate value for cases where _Float128 > is used. > (__format::__float128_t): Renamed to __format::_flt128_t. > (std::formatter<_Float128, _CharT>): Define always if there is > formattable > 128bit float. > (std::formatter<__float128, _CharT>): Define. > (_Arg_type::_Arg_f128): Rename to _Arg_float128 and adjust value. > (_Arg_type::_Arg_ibm128): Change value to _Arg_ldbl. > (_Arg_type::_Arg_ieee128): Define as alias to _Arg_float128. > (_Arg_value::_M_f128): Replaced with _M_ieee128 and _M_float128. > (_Arg_value::_M_ieee128, _Arg_value::_M_float128, > _Arg_value::_M_bf16) > (_Arg_value::_M_f16, _Arg_value::_M_f32, _Arg_value::_M_f64): > Define. > (_Arg_value::_S_get, basic_format_arg::_S_to_enum): Handle > __bflt16, > _Float16, _Float32, _Float64, and __float128 types. > (basic_format_arg::_S_to_arg_type): Preserve _bflt16, _Float16, > _Float32, _Float64 and __float128 types. > (basic_format_arg::_M_visit): Hadndle _Arg_float128, _Arg_ieee128, > _Arg_b16, _Arg_f16, _Arg_f32, _Arg_f64. > * testsuite/std/format/arguments/args.cc: Updated to illustrate > that > extended floating point types use handles now. Added test for > __float128. > * testsuite/std/format/parse_ctx.cc: Extended test to cover class > to > check_dynamic_spec with floating point types and handles. > --- > Tested on x86_64-linux and powerpc64le-unknown-linux-gnu. > Running additional test on powerpc64le with > unix\{-mabi=ibmlongdouble,-mabi=ieeelongdouble,-mno-float128}. > The -mabi=ibmlongdouble and -mabi=ieeelongdouble passed. The -mno-float128 seem to be not handled on trunk due use of __float128 instead of __ieee128 here: https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/std/charconv;h=dda49ce72d0b53c7a6e86c2e3fb510d0218fd5a6;hb=HEAD#l878 Interestingly from chars use __ieee128: https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/std/charconv;h=dda49ce72d0b53c7a6e86c2e3fb510d0218fd5a6;hb=HEAD#l742 > OK for trunk? > > libstdc++-v3/include/std/format | 217 ++++++++++++------ > .../testsuite/std/format/arguments/args.cc | 45 ++-- > .../testsuite/std/format/parse_ctx.cc | 72 +++++- > 3 files changed, 227 insertions(+), 107 deletions(-) > > diff --git a/libstdc++-v3/include/std/format > b/libstdc++-v3/include/std/format > index 054ce350440..73819f52f50 100644 > --- a/libstdc++-v3/include/std/format > +++ b/libstdc++-v3/include/std/format > @@ -1863,20 +1863,24 @@ namespace __format > _Spec<_CharT> _M_spec{}; > }; > > +#ifdef __BFLT16_DIG__ > + using __bflt16_t = decltype(0.0bf16); > +#endif > + > // Decide how 128-bit floating-point types should be formatted (or not). > - // When supported, the typedef __format::__float128_t is the type that > - // format arguments should be converted to for storage in > basic_format_arg. > + // When supported, the typedef __format::__flt128_t is the type that > + // format arguments should be converted before formatting code. > // Define the macro _GLIBCXX_FORMAT_F128 to say they're supported. > - // _GLIBCXX_FORMAT_F128=1 means __float128, _Float128 etc. will be > formatted > - // by converting them to long double (or __ieee128 for powerpc64le). > - // _GLIBCXX_FORMAT_F128=2 means basic_format_arg needs to enable > explicit > - // support for _Float128, rather than formatting it as another type. > + // The __float128, _Float128 will be formatted by converting them to: > + // __ieee128 (same as __float128) _GLIBCXX_FORMAT_F128=1, > + // long double if _GLIBCXX_FORMAT_F128=2, > + // _Float128 if _GLIBCXX_FORMAT_F128=3. > #undef _GLIBCXX_FORMAT_F128 > > #ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > > // Format 128-bit floating-point types using __ieee128. > - using __float128_t = __ieee128; > + using __flt128_t = __ieee128; > # define _GLIBCXX_FORMAT_F128 1 > > #ifdef __LONG_DOUBLE_IEEE128__ > @@ -1910,14 +1914,14 @@ namespace __format > #elif defined _GLIBCXX_LDOUBLE_IS_IEEE_BINARY128 > > // Format 128-bit floating-point types using long double. > - using __float128_t = long double; > -# define _GLIBCXX_FORMAT_F128 1 > + using __flt128_t = long double; > +# define _GLIBCXX_FORMAT_F128 2 > > #elif __FLT128_DIG__ && defined(_GLIBCXX_HAVE_FLOAT128_MATH) > > // Format 128-bit floating-point types using _Float128. > - using __float128_t = _Float128; > -# define _GLIBCXX_FORMAT_F128 2 > + using __flt128_t = _Float128; > +# define _GLIBCXX_FORMAT_F128 3 > > # if __cplusplus == 202002L > // These overloads exist in the library, but are not declared for C++20. > @@ -2947,8 +2951,8 @@ namespace __format > }; > #endif > > -#if defined(__FLT128_DIG__) && _GLIBCXX_FORMAT_F128 == 1 > - // Reuse __formatter_fp<C>::format<__float128_t, Out> for _Float128. > +#if defined(__FLT128_DIG__) && _GLIBCXX_FORMAT_F128 > + // Use __formatter_fp<C>::format<__format::flt128_t, Out> for _Float128. > template<__format::__char _CharT> > struct formatter<_Float128, _CharT> > { > @@ -2962,17 +2966,45 @@ namespace __format > 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); } > + { return _M_f.format((__format::__flt128_t)__u, __fc); } > + > + private: > + __format::__formatter_fp<_CharT> _M_f; > + }; > +#endif > + > +#if defined(__SIZEOF_FLOAT128__) && _GLIBCXX_FORMAT_F128 != 1 > + // Reuse __formatter_fp<C>::format<__format::_flt128_t, Out> for > __float128. > + // This formatter is not declared if _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > is true, > + // as __float128 when present is same type as __ieee128, which may be > same as > + // long double. > + 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::__flt128_t)__u, __fc); } > > private: > __format::__formatter_fp<_CharT> _M_f; > + > + static_assert( !is_same_v<__float128, long double>, > + "This specialization should not be used for long > double" ); > }; > #endif > > #if defined(__STDCPP_BFLOAT16_T__) && > defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > // Reuse __formatter_fp<C>::format<float, Out> for bfloat16_t. > template<__format::__char _CharT> > - struct formatter<__gnu_cxx::__bfloat16_t, _CharT> > + struct formatter<__format::__bflt16_t, _CharT> > { > formatter() = default; > > @@ -3795,16 +3827,14 @@ namespace __format > enum _Arg_t : unsigned char { > _Arg_none, _Arg_bool, _Arg_c, _Arg_i, _Arg_u, _Arg_ll, _Arg_ull, > _Arg_flt, _Arg_dbl, _Arg_ldbl, _Arg_str, _Arg_sv, _Arg_ptr, > _Arg_handle, > - _Arg_i128, _Arg_u128, > - _Arg_bf16, _Arg_f16, _Arg_f32, _Arg_f64, // These are unused. > + _Arg_i128, _Arg_u128, _Arg_float128, > + _Arg_bf16, _Arg_f16, _Arg_f32, _Arg_f64, > + _Arg_max_, > + > #ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > - _Arg_next_value_, > - _Arg_f128 = _Arg_ldbl, > - _Arg_ibm128 = _Arg_next_value_, > -#else > - _Arg_f128, > + _Arg_ibm128 = _Arg_ldbl, > + _Arg_ieee128 = _Arg_float128, > #endif > - _Arg_max_ > }; > > template<typename _Context> > @@ -3831,6 +3861,12 @@ namespace __format > double _M_dbl; > #ifndef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT // No long double if it's > ambiguous. > long double _M_ldbl; > +#else > + __ibm128 _M_ibm128; > + __ieee128 _M_ieee128; > +#endif > +#ifdef __SIZEOF_FLOAT128__ > + __float128 _M_float128; > #endif > const _CharT* _M_str; > basic_string_view<_CharT> _M_sv; > @@ -3840,11 +3876,17 @@ namespace __format > __int128 _M_i128; > unsigned __int128 _M_u128; > #endif > -#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > - __ieee128 _M_f128; > - __ibm128 _M_ibm128; > -#elif _GLIBCXX_FORMAT_F128 == 2 > - __float128_t _M_f128; > +#ifdef __BFLT16_DIG__ > + __bflt16_t _M_bf16; > +#endif > +#ifdef __FLT16_DIG__ > + _Float16 _M_f16; > +#endif > +#ifdef __FLT32_DIG__ > + _Float32 _M_f32; > +#endif > +#ifdef __FLT64_DIG__ > + _Float64 _M_f64; > #endif > }; > > @@ -3882,10 +3924,14 @@ namespace __format > else if constexpr (is_same_v<_Tp, long double>) > return __u._M_ldbl; > #else > - else if constexpr (is_same_v<_Tp, __ieee128>) > - return __u._M_f128; > else if constexpr (is_same_v<_Tp, __ibm128>) > return __u._M_ibm128; > + else if constexpr (is_same_v<_Tp, __ieee128>) > + return __u._M_ieee128; > +#endif > +#ifdef __SIZEOF_FLOAT128__ > + else if constexpr (is_same_v<_Tp, __float128>) > + return __u._M_float128; > #endif > else if constexpr (is_same_v<_Tp, const _CharT*>) > return __u._M_str; > @@ -3899,9 +3945,21 @@ namespace __format > else if constexpr (is_same_v<_Tp, unsigned __int128>) > return __u._M_u128; > #endif > -#if _GLIBCXX_FORMAT_F128 == 2 > - else if constexpr (is_same_v<_Tp, __float128_t>) > - return __u._M_f128; > +#ifdef __BFLT16_DIG__ > + else if constexpr (is_same_v<_Tp, __bflt16_t>) > + return __u._M_bf16; > +#endif > +#ifdef __FLT16_DIG__ > + else if constexpr (is_same_v<_Tp, _Float16>) > + return __u._M_f16; > +#endif > +#ifdef __FLT32_DIG__ > + else if constexpr (is_same_v<_Tp, _Float32>) > + return __u._M_f32; > +#endif > +#ifdef __FLT64_DIG__ > + else if constexpr (is_same_v<_Tp, _Float64>) > + return __u._M_f64; > #endif > else if constexpr (derived_from<_Tp, _HandleBase>) > return static_cast<_Tp&>(__u._M_handle); > @@ -4080,36 +4138,25 @@ namespace __format > else if constexpr (is_same_v<_Td, __ieee128>) > return type_identity<__ieee128>(); > #endif > - > -#if defined(__FLT16_DIG__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > - else if constexpr (is_same_v<_Td, _Float16>) > - return type_identity<float>(); > +#if defined(__SIZEOF_FLOAT128__) && _GLIBCXX_FORMAT_F128 > + else if constexpr (is_same_v<_Td, __float128>) > + return type_identity<__float128>(); > #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>(); > +#if defined(__STDCPP_BFLOAT16_T__) && > defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > + else if constexpr (is_same_v<_Td, __format::__bflt16_t>) > + return type_identity<__format::__bflt16_t>(); > +#endif > +#if defined(__STDCPP_FLOAT16_T__) && > defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > + else if constexpr (is_same_v<_Td, _Float16>) > + return type_identity<_Float16>(); > #endif > - > #if defined(__FLT32_DIG__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > else if constexpr (is_same_v<_Td, _Float32>) > - return type_identity<float>(); > + return type_identity<_Float32>(); > #endif > - > #if defined(__FLT64_DIG__) && defined(_GLIBCXX_DOUBLE_IS_IEEE_BINARY64) > else if constexpr (is_same_v<_Td, _Float64>) > - return type_identity<double>(); > -#endif > - > -#if _GLIBCXX_FORMAT_F128 > -# if __FLT128_DIG__ > - else if constexpr (is_same_v<_Td, _Float128>) > - return type_identity<__format::__float128_t>(); > -# endif > -# if __SIZEOF_FLOAT128__ > - else if constexpr (is_same_v<_Td, __float128>) > - return type_identity<__format::__float128_t>(); > -# endif > + return type_identity<_Float64>(); > #endif > else if constexpr (__is_specialization_of<_Td, basic_string_view> > || __is_specialization_of<_Td, basic_string>) > @@ -4165,7 +4212,27 @@ namespace __format > else if constexpr (is_same_v<_Tp, __ibm128>) > return _Arg_ibm128; > else if constexpr (is_same_v<_Tp, __ieee128>) > - return _Arg_f128; > + return _Arg_ieee128; > +#endif > +#if defined(__SIZEOF_FLOAT128__) && _GLIBCXX_FORMAT_F128 > + else if constexpr (is_same_v<_Tp, __float128>) > + return _Arg_float128; > +#endif > +#if defined(__STDCPP_BFLOAT16_T__) && > defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > + else if constexpr (is_same_v<_Tp, __format::__bflt16_t>) > + return _Arg_bf16; > +#endif > +#if defined(__STDCPP_FLOAT16_T__) && > defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > + else if constexpr (is_same_v<_Tp, _Float16>) > + return _Arg_f16; > +#endif > +#if defined(__FLT32_DIG__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > + else if constexpr (is_same_v<_Tp, _Float32>) > + return _Arg_f32; > +#endif > +#if defined(__FLT64_DIG__) && defined(_GLIBCXX_DOUBLE_IS_IEEE_BINARY64) > + else if constexpr (is_same_v<_Tp, _Float64>) > + return _Arg_f64; > #endif > else if constexpr (is_same_v<_Tp, const _CharT*>) > return _Arg_str; > @@ -4179,11 +4246,6 @@ namespace __format > else if constexpr (is_same_v<_Tp, unsigned __int128>) > return _Arg_u128; > #endif > - > -#if _GLIBCXX_FORMAT_F128 == 2 > - else if constexpr (is_same_v<_Tp, __format::__float128_t>) > - return _Arg_f128; > -#endif > else if constexpr (is_same_v<_Tp, handle>) > return _Arg_handle; > } > @@ -4256,13 +4318,33 @@ namespace __format > #ifndef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > case _Arg_ldbl: > return std::forward<_Visitor>(__vis)(_M_val._M_ldbl); > +#if defined(__SIZEOF_FLOAT128__) && _GLIBCXX_FORMAT_F128 > + case _Arg_float128: > + return std::forward<_Visitor>(__vis)(_M_val._M_float128); > +#endif > #else > - case _Arg_f128: > - return std::forward<_Visitor>(__vis)(_M_val._M_f128); > case _Arg_ibm128: > return std::forward<_Visitor>(__vis)(_M_val._M_ibm128); > + case _Arg_ieee128: > + return std::forward<_Visitor>(__vis)(_M_val._M_ieee128); > #endif > +#if defined(__STDCPP_BFLOAT16_T__) && > defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > + case _Arg_bf16: > + return std::forward<_Visitor>(__vis)(_M_val._M_bf16); > +#endif > +#if defined(__STDCPP_FLOAT16_T__) && > defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > + case _Arg_f16: > + return std::forward<_Visitor>(__vis)(_M_val._M_f16); > +#endif > +#if defined(__FLT32_DIG__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) > + case _Arg_f32: > + return std::forward<_Visitor>(__vis)(_M_val._M_f32); > #endif > +#if defined(__FLT64_DIG__) && defined(_GLIBCXX_DOUBLE_IS_IEEE_BINARY64) > + case _Arg_f64: > + return std::forward<_Visitor>(__vis)(_M_val._M_f64); > +#endif > +#endif // __glibcxx_to_chars > case _Arg_str: > return std::forward<_Visitor>(__vis)(_M_val._M_str); > case _Arg_sv: > @@ -4280,14 +4362,7 @@ namespace __format > case _Arg_u128: > return std::forward<_Visitor>(__vis)(_M_val._M_u128); > #endif > - > -#if _GLIBCXX_FORMAT_F128 == 2 > - case _Arg_f128: > - return std::forward<_Visitor>(__vis)(_M_val._M_f128); > -#endif > - > default: > - // _Arg_f16 etc. > __builtin_unreachable(); > } > } > diff --git a/libstdc++-v3/testsuite/std/format/arguments/args.cc > b/libstdc++-v3/testsuite/std/format/arguments/args.cc > index 4c50bc74319..60296753919 100644 > --- a/libstdc++-v3/testsuite/std/format/arguments/args.cc > +++ b/libstdc++-v3/testsuite/std/format/arguments/args.cc > @@ -164,24 +164,6 @@ void test_visited_as_handle() > #endif > } > > -template<typename E, typename S> > -void test_visited_as() > -{ > - auto v = static_cast<S>(1.0); > - auto store = std::make_format_args(v); > - std::format_args args = store; > - > - auto is_expected_val = [v](auto arg) { > - if constexpr (std::is_same_v<decltype(arg), E>) > - return arg == static_cast<E>(v); > - return false; > - }; > - VERIFY( std::visit_format_arg(is_expected_val, args.get(0)) ); > -#if __cpp_lib_format >= 202306L // C++26 adds std::basic_format_arg::visit > - VERIFY( args.get(0).visit(is_expected_val) ); > -#endif > -} > - > template<typename T> > concept can_format = std::is_default_constructible_v<std::formatter<T, > char>>; > > @@ -195,30 +177,31 @@ int main() > test_visited_as_handle<__int128>(); > test_visited_as_handle<unsigned __int128>(); > #endif > -// TODO: This should be visited as handle. > -#ifdef __STDCPP_FLOAT16_T__ > - if constexpr (can_format<_Float16>) > - test_visited_as<float, _Float16>(); > -#endif > -#ifdef __STDCPP_BFLOAT16_T__ > +#ifdef __BFLT16_DIG__ > if constexpr (can_format<__gnu_cxx::__bfloat16_t>) > - test_visited_as<float, __gnu_cxx::__bfloat16_t>(); > + test_visited_as_handle<__gnu_cxx::__bfloat16_t>(); > +#endif > +#ifdef __FLT16_DIG__ > + if constexpr (can_format<_Float16>) > + test_visited_as_handle<_Float16>(); > #endif > #ifdef __FLT32_DIG__ > if constexpr (can_format<_Float32>) > - test_visited_as<float, _Float32>(); > + test_visited_as_handle<_Float32>(); > #endif > #ifdef __FLT64_DIG__ > if constexpr (can_format<_Float64>) > - test_visited_as<double, _Float64>(); > + test_visited_as_handle<_Float64>(); > #endif > #ifdef __FLT128_DIG__ > if constexpr (can_format<_Float128>) > -# ifdef _GLIBCXX_LDOUBLE_IS_IEEE_BINARY128 > - test_visited_as<long double, _Float128>(); > -# else > test_visited_as_handle<_Float128>(); > -# endif > +#endif > +#ifdef __SIZEOF_FLOAT128__ > + // __ieee128 is same type as __float128, and may be long double > + if constexpr (!std::is_same_v<__float128, long double>) > + if constexpr (can_format<__float128>) > + test_visited_as_handle<__float128>(); > #endif > #ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > if constexpr (!std::is_same_v<__ieee128, long double>) > diff --git a/libstdc++-v3/testsuite/std/format/parse_ctx.cc > b/libstdc++-v3/testsuite/std/format/parse_ctx.cc > index b5dd7cdba78..adafc58c183 100644 > --- a/libstdc++-v3/testsuite/std/format/parse_ctx.cc > +++ b/libstdc++-v3/testsuite/std/format/parse_ctx.cc > @@ -443,6 +443,8 @@ test_custom() > } > > #if __cpp_lib_format >= 202305 > +#include <stdfloat> > + > struct X { }; > > template<> > @@ -458,13 +460,20 @@ struct std::formatter<X, char> > if (spec == "int") > { > pc.check_dynamic_spec_integral(pc.next_arg_id()); > - integer = true; > + type = Type::integral; > } > else if (spec == "str") > { > pc.check_dynamic_spec_string(pc.next_arg_id()); > - integer = false; > + type = Type::string; > + } > + else if (spec == "float") > + { > + pc.check_dynamic_spec<float, double, long double>(pc.next_arg_id()); > + type = Type::floating; > } > + else if (spec == "other") > + type = Type::other; > else > throw std::format_error("invalid format-spec"); > return pc.begin() + spec.size(); > @@ -474,13 +483,44 @@ struct std::formatter<X, char> > format(X, std::format_context& c) const > { > std::visit_format_arg([this]<typename T>(T) { // { dg-warning > "deprecated" "" { target c++26 } } > - if (is_integral_v<T> != this->integer) > - throw std::format_error("invalid argument type"); > + constexpr bool is_handle > + = > std::is_same_v<std::basic_format_arg<std::format_context>::handle, T>; > + constexpr bool is_integral > + = std::is_same_v<int, T> || std::is_same_v<unsigned int, T> > + || is_same_v<long long, T> || std::is_same_v<unsigned long > long, T>; > + constexpr bool is_string > + = std::is_same_v<const char*, T> || > std::is_same_v<std::string_view, T>; > + constexpr bool is_floating > + = std::is_same_v<float, T> || std::is_same_v<double, T> > + || std::is_same_v<long double, T>; > + switch (this->type) > + { > + case Type::other: > + if (is_handle) return; > + break; > + case Type::integral: > + if (is_integral) return; > + break; > + case Type::string: > + if (is_string) return; > + break; > + case Type::floating: > + if (is_floating) return; > + break; > + } > + throw std::format_error("invalid argument type"); > }, c.arg(1)); > return c.out(); > } > private: > - bool integer = false; > + enum class Type > + { > + other, > + integral, > + string, > + floating, > + }; > + Type type = Type::other; > }; > #endif > > @@ -497,6 +537,28 @@ test_dynamic_type_check() > > (void) std::format("{:int}", X{}, 42L); > (void) std::format("{:str}", X{}, "H2G2"); > + (void) std::format("{:float}", X{}, 10.0); > + > +#ifdef __STDCPP_FLOAT16_T__ > + if constexpr (std::formattable<std::bfloat16_t, char>) > + (void) std::format("{:other}", X{}, 10.0bf16); > +#endif > +#ifdef __STDCPP_FLOAT16_T__ > + if constexpr (std::formattable<std::float16_t, char>) > + (void) std::format("{:other}", X{}, 10.0f16); > +#endif > +#ifdef __STDCPP_FLOAT32_T__ > + if constexpr (std::formattable<std::float32_t, char>) > + (void) std::format("{:other}", X{}, 10.0f32); > +#endif > +#ifdef __STDCPP_FLOAT64_T__ > + if constexpr (std::formattable<std::float64_t, char>) > + (void) std::format("{:other}", X{}, 10.0f64); > +#endif > +#ifdef __STDCPP_FLOAT128_T__ > + if constexpr (std::formattable<std::float128_t, char>) > + (void) std::format("{:other}", X{}, 10.0f128); > +#endif > #endif > } > > -- > 2.49.0 > >