/// It could be packed in 4-bits but that means a new type directly becomes an
/// ABI break. The packed type is 64-bit so this reduces the maximum number of
/// packed elements from 16 to 12.
+///
+/// @note Some members of this enum are an extension. These extensions need
+/// special behaviour in visit_format_arg. There they need to be wrapped in a
+/// handle to satisfy the user observable behaviour. The internal function
+/// __visit_format_arg doesn't do this wrapping. So in the format functions
+/// this function is used to avoid unneeded overhead.
enum class _LIBCPP_ENUM_VIS __arg_t : uint8_t {
__none,
__boolean,
__char_type,
__int,
__long_long,
- __i128,
+ __i128, // extension
__unsigned,
__unsigned_long_long,
- __u128,
+ __u128, // extension
__float,
__double,
__long_double,
} // namespace __format
+// This function is not user obervable, so it can directly use the non-standard
+// types of the "variant". See __arg_t for more details.
template <class _Visitor, class _Context>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT decltype(auto) visit_format_arg(_Visitor&& __vis,
- basic_format_arg<_Context> __arg) {
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT decltype(auto)
+__visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg) {
switch (__arg.__type_) {
case __format::__arg_t::__none:
return _VSTD::invoke(_VSTD::forward<_Visitor>(__vis), __arg.__value_.__monostate_);
typename __basic_format_arg_value<_Context>::__handle& __handle_;
};
+// This function is user facing, so it must wrap the non-standard types of
+// the "variant" in a handle to stay conforming. See __arg_t for more details.
+template <class _Visitor, class _Context>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT decltype(auto)
+visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg) {
+ switch (__arg.__type_) {
+# ifndef _LIBCPP_HAS_NO_INT128
+ case __format::__arg_t::__i128: {
+ typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__i128_};
+ return _VSTD::invoke(_VSTD::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h});
+ }
+
+ case __format::__arg_t::__u128: {
+ typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__u128_};
+ return _VSTD::invoke(_VSTD::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h});
+ }
+# endif
+ default:
+ return _VSTD::__visit_format_arg(_VSTD::forward<_Visitor>(__vis), __arg);
+ }
+}
+
#endif //_LIBCPP_STD_VER > 17
_LIBCPP_END_NAMESPACE_STD
__format::__compile_time_validate_integral(__ctx.arg(__formatter.__parser_.__precision_));
}
+// This function is not user facing, so it can directly use the non-standard types of the "variant".
template <class _CharT>
_LIBCPP_HIDE_FROM_ABI constexpr void __compile_time_visit_format_arg(basic_format_parse_context<_CharT>& __parse_ctx,
__compile_time_basic_format_context<_CharT>& __ctx,
else
__format::__compile_time_visit_format_arg(__parse_ctx, __ctx, __type);
} else
- _VSTD::visit_format_arg(
+ _VSTD::__visit_format_arg(
[&](auto __arg) {
if constexpr (same_as<decltype(__arg), monostate>)
__throw_format_error("Argument index out of bounds");
template <class _Context>
_LIBCPP_HIDE_FROM_ABI constexpr uint32_t
__substitute_arg_id(basic_format_arg<_Context> __format_arg) {
- return visit_format_arg(
+ // [format.string.std]/8
+ // If the corresponding formatting argument is not of integral type...
+ // This wording allows char and bool too. LWG-3720 changes the wording to
+ // If the corresponding formatting argument is not of standard signed or
+ // unsigned integer type,
+ // This means the 128-bit will not be valid anymore.
+ // TODO FMT Verify this resolution is accepted and add a test to verify
+ // 128-bit integrals fail and switch to visit_format_arg.
+ return _VSTD::__visit_format_arg(
[](auto __arg) -> uint32_t {
using _Type = decltype(__arg);
if constexpr (integral<_Type>) {
auto store = std::make_format_args<Context>(value);
std::basic_format_args<Context> format_args{store};
- assert(format_args.__size() == 1);
+ LIBCPP_ASSERT(format_args.__size() == 1);
assert(format_args.get(0));
auto result = std::visit_format_arg(
assert(static_cast<ct>(result) == static_cast<ct>(value));
}
+// Some types, as an extension, are stored in the variant. The Standard
+// requires them to be observed as a handle.
+template <class Context, class T>
+void test_handle(T value) {
+ auto store = std::make_format_args<Context>(value);
+ std::basic_format_args<Context> format_args{store};
+
+ LIBCPP_ASSERT(format_args.__size() == 1);
+ assert(format_args.get(0));
+
+ std::visit_format_arg(
+ [](auto a) { assert((std::is_same_v<decltype(a), typename std::basic_format_arg<Context>::handle>)); },
+ format_args.get(0));
+}
+
// Test specific for string and string_view.
//
// Since both result in a string_view there's no need to pass this as a
auto store = std::make_format_args<Context>(value);
std::basic_format_args<Context> format_args{store};
- assert(format_args.__size() == 1);
+ LIBCPP_ASSERT(format_args.__size() == 1);
assert(format_args.get(0));
using CharT = typename Context::char_type;
test<Context, long long, long long>(std::numeric_limits<long long>::max());
#ifndef TEST_HAS_NO_INT128
- test<Context, __int128_t, __int128_t>(std::numeric_limits<__int128_t>::min());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<long long>::min());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<long>::min());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<int>::min());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<short>::min());
- test<Context, __int128_t, __int128_t>(
- std::numeric_limits<signed char>::min());
- test<Context, __int128_t, __int128_t>(0);
- test<Context, __int128_t, __int128_t>(
- std::numeric_limits<signed char>::max());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<short>::max());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<int>::max());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<long>::max());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<long long>::max());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<__int128_t>::max());
-#endif
+ test_handle<Context, __int128_t>(0);
+#endif // TEST_HAS_NO_INT128
// Test unsigned integer types.
std::numeric_limits<unsigned long long>::max());
#ifndef TEST_HAS_NO_INT128
- test<Context, __uint128_t, __uint128_t>(0);
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<unsigned char>::max());
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<unsigned short>::max());
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<unsigned int>::max());
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<unsigned long>::max());
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<unsigned long long>::max());
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<__uint128_t>::max());
-#endif
+ test_handle<Context, __uint128_t>(0);
+#endif // TEST_HAS_NO_INT128
// Test floating point types.
format_args.get(0));
}
+// Some types, as an extension, are stored in the variant. The Standard
+// requires them to be observed as a handle.
+template <class Context, class T>
+void test_handle(T value) {
+ auto store = std::make_format_args<Context>(value);
+ std::basic_format_args<Context> format_args{store};
+
+ std::visit_format_arg(
+ [](auto a) { assert((std::is_same_v<decltype(a), typename std::basic_format_arg<Context>::handle>)); },
+ format_args.get(0));
+}
+
// Test specific for string and string_view.
//
// Since both result in a string_view there's no need to pass this as a
test<Context, long long, long long>(std::numeric_limits<long long>::max());
#ifndef TEST_HAS_NO_INT128
- test<Context, __int128_t, __int128_t>(std::numeric_limits<__int128_t>::min());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<long long>::min());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<long>::min());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<int>::min());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<short>::min());
- test<Context, __int128_t, __int128_t>(
- std::numeric_limits<signed char>::min());
- test<Context, __int128_t, __int128_t>(0);
- test<Context, __int128_t, __int128_t>(
- std::numeric_limits<signed char>::max());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<short>::max());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<int>::max());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<long>::max());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<long long>::max());
- test<Context, __int128_t, __int128_t>(std::numeric_limits<__int128_t>::max());
-#endif
+ test_handle<Context, __int128_t>(0);
+#endif // TEST_HAS_NO_INT128
// Test unsigned integer types.
std::numeric_limits<unsigned long long>::max());
#ifndef TEST_HAS_NO_INT128
- test<Context, __uint128_t, __uint128_t>(0);
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<unsigned char>::max());
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<unsigned short>::max());
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<unsigned int>::max());
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<unsigned long>::max());
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<unsigned long long>::max());
- test<Context, __uint128_t, __uint128_t>(
- std::numeric_limits<__uint128_t>::max());
-#endif
+ test_handle<Context, __uint128_t>(0);
+#endif // TEST_HAS_NO_INT128
// Test floating point types.