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 fixed width floating-point types 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, if present, is same type as __ieee128. 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) 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 __format::__flt128_t is always provided, when type is available. It is still correct when __format::__flt128_t is _Float128. 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): Handle _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. --- I believe I have fixed all the typos. 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 b3192cf2868..f0b0252255d 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 to before passing them to __formatter_fp. // 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) when _GLIBCXX_FORMAT_F128=1, + // long double when _GLIBCXX_FORMAT_F128=2, + // _Float128 when _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; @@ -3835,16 +3867,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> @@ -3871,6 +3901,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; @@ -3880,11 +3916,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 }; @@ -3922,10 +3964,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; @@ -3939,9 +3985,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); @@ -4120,36 +4178,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>) @@ -4205,7 +4252,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; @@ -4219,11 +4286,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; } @@ -4296,13 +4358,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: @@ -4320,14 +4402,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