Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 149 additions & 125 deletions libcxx/include/__format/format_arg.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ namespace __format {
/// @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
/// __directly_visit_format_arg doesn't do this wrapping. So in the format functions
/// this function is used to avoid unneeded overhead.
enum class __arg_t : uint8_t {
__none,
Expand Down Expand Up @@ -98,10 +98,86 @@ _LIBCPP_HIDE_FROM_ABI constexpr __arg_t __get_packed_type(uint64_t __types, size

} // namespace __format

// This function is not user observable, so it can directly use the non-standard
// types of the "variant". See __arg_t for more details.
/// Contains the values used in basic_format_arg.
///
/// This is a separate type so it's possible to store the values and types in
/// separate arrays.
template <class _Context>
class __basic_format_arg_value {
using _CharT _LIBCPP_NODEBUG = typename _Context::char_type;

public:
/// Contains the implementation for basic_format_arg::handle.
struct __handle {
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI explicit __handle(_Tp& __v) noexcept
: __ptr_(std::addressof(__v)),
__format_([](basic_format_parse_context<_CharT>& __parse_ctx, _Context& __ctx, const void* __ptr) {
using _Dp = remove_const_t<_Tp>;
using _Qp = conditional_t<__formattable_with<const _Dp, _Context>, const _Dp, _Dp>;
static_assert(__formattable_with<_Qp, _Context>, "Mandated by [format.arg]/10");

typename _Context::template formatter_type<_Dp> __f;
__parse_ctx.advance_to(__f.parse(__parse_ctx));
__ctx.advance_to(__f.format(*const_cast<_Qp*>(static_cast<const _Dp*>(__ptr)), __ctx));
}) {}

const void* __ptr_;
void (*__format_)(basic_format_parse_context<_CharT>&, _Context&, const void*);
};

union {
monostate __monostate_;
bool __boolean_;
_CharT __char_type_;
int __int_;
unsigned __unsigned_;
long long __long_long_;
unsigned long long __unsigned_long_long_;
# if _LIBCPP_HAS_INT128
__int128_t __i128_;
__uint128_t __u128_;
# endif
float __float_;
double __double_;
long double __long_double_;
const _CharT* __const_char_type_ptr_;
basic_string_view<_CharT> __string_view_;
const void* __ptr_;
__handle __handle_;
};

// These constructors contain the exact storage type used. If adjustments are
// required, these will be done in __create_format_arg.

_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value() noexcept : __monostate_() {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(bool __value) noexcept : __boolean_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(_CharT __value) noexcept : __char_type_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(int __value) noexcept : __int_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(unsigned __value) noexcept : __unsigned_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(long long __value) noexcept : __long_long_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(unsigned long long __value) noexcept
: __unsigned_long_long_(__value) {}
# if _LIBCPP_HAS_INT128
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(__int128_t __value) noexcept : __i128_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(__uint128_t __value) noexcept : __u128_(__value) {}
# endif
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(float __value) noexcept : __float_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(double __value) noexcept : __double_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(long double __value) noexcept : __long_double_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(const _CharT* __value) noexcept : __const_char_type_ptr_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(basic_string_view<_CharT> __value) noexcept
: __string_view_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(const void* __value) noexcept : __ptr_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(__handle&& __value) noexcept : __handle_(std::move(__value)) {}
};

// This function is not user observable, so it can directly use the non-standard types of the "variant".
// See __arg_t for more details. For direct visitation, see https://reviews.llvm.org/D138052.
// TODO: Investigate why GCC 15 hangs if something like std::__visit_format_arg<__direct::__yes>(...) is used, and fuse
// __visit_format_arg and __directly_visit_format_arg once GCC no longer hangs.
template <class _Visitor, class _Context>
_LIBCPP_HIDE_FROM_ABI decltype(auto) __visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg) {
_LIBCPP_HIDE_FROM_ABI decltype(auto) __directly_visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg) {
switch (__arg.__type_) {
case __format::__arg_t::__none:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__monostate_);
Expand Down Expand Up @@ -149,6 +225,64 @@ _LIBCPP_HIDE_FROM_ABI decltype(auto) __visit_format_arg(_Visitor&& __vis, basic_
__libcpp_unreachable();
}

// __visit_format_arg is same as __directly_visit_format_arg except for indirectly visitation of 128-bit integers.
// Per [format.arg], the variant alternative types are fully specified, so we need to avoid direct visitation of 128-bit
// extended integer types when the visitor is user-provided.
template <class _Visitor, class _Context>
_LIBCPP_HIDE_FROM_ABI decltype(auto) __visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg) {
switch (__arg.__type_) {
case __format::__arg_t::__none:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__monostate_);
case __format::__arg_t::__boolean:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__boolean_);
case __format::__arg_t::__char_type:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__char_type_);
case __format::__arg_t::__int:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__int_);
case __format::__arg_t::__long_long:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__long_long_);
case __format::__arg_t::__i128:
# if _LIBCPP_HAS_INT128
{
typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__i128_};
return std::invoke(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h});
}
# else
__libcpp_unreachable();
# endif
case __format::__arg_t::__unsigned:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__unsigned_);
case __format::__arg_t::__unsigned_long_long:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__unsigned_long_long_);
case __format::__arg_t::__u128:
# if _LIBCPP_HAS_INT128
{
typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__u128_};
return std::invoke(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h});
}
# else
__libcpp_unreachable();
# endif
case __format::__arg_t::__float:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__float_);
case __format::__arg_t::__double:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__double_);
case __format::__arg_t::__long_double:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__long_double_);
case __format::__arg_t::__const_char_type_ptr:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__const_char_type_ptr_);
case __format::__arg_t::__string_view:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__string_view_);
case __format::__arg_t::__ptr:
return std::invoke(std::forward<_Visitor>(__vis), __arg.__value_.__ptr_);
case __format::__arg_t::__handle:
return std::invoke(
std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__arg.__value_.__handle_});
}

__libcpp_unreachable();
}

# if _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_EXPLICIT_THIS_PARAMETER

template <class _Rp, class _Visitor, class _Context>
Expand All @@ -166,7 +300,10 @@ _LIBCPP_HIDE_FROM_ABI _Rp __visit_format_arg(_Visitor&& __vis, basic_format_arg<
return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__long_long_);
case __format::__arg_t::__i128:
# if _LIBCPP_HAS_INT128
return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__i128_);
{
typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__i128_};
return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h});
}
# else
__libcpp_unreachable();
# endif
Expand All @@ -176,7 +313,10 @@ _LIBCPP_HIDE_FROM_ABI _Rp __visit_format_arg(_Visitor&& __vis, basic_format_arg<
return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__unsigned_long_long_);
case __format::__arg_t::__u128:
# if _LIBCPP_HAS_INT128
return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__u128_);
{
typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__u128_};
return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h});
}
# else
__libcpp_unreachable();
# endif
Expand All @@ -202,80 +342,6 @@ _LIBCPP_HIDE_FROM_ABI _Rp __visit_format_arg(_Visitor&& __vis, basic_format_arg<

# endif // _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_EXPLICIT_THIS_PARAMETER

/// Contains the values used in basic_format_arg.
///
/// This is a separate type so it's possible to store the values and types in
/// separate arrays.
template <class _Context>
class __basic_format_arg_value {
using _CharT _LIBCPP_NODEBUG = typename _Context::char_type;

public:
/// Contains the implementation for basic_format_arg::handle.
struct __handle {
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI explicit __handle(_Tp& __v) noexcept
: __ptr_(std::addressof(__v)),
__format_([](basic_format_parse_context<_CharT>& __parse_ctx, _Context& __ctx, const void* __ptr) {
using _Dp = remove_const_t<_Tp>;
using _Qp = conditional_t<__formattable_with<const _Dp, _Context>, const _Dp, _Dp>;
static_assert(__formattable_with<_Qp, _Context>, "Mandated by [format.arg]/10");

typename _Context::template formatter_type<_Dp> __f;
__parse_ctx.advance_to(__f.parse(__parse_ctx));
__ctx.advance_to(__f.format(*const_cast<_Qp*>(static_cast<const _Dp*>(__ptr)), __ctx));
}) {}

const void* __ptr_;
void (*__format_)(basic_format_parse_context<_CharT>&, _Context&, const void*);
};

union {
monostate __monostate_;
bool __boolean_;
_CharT __char_type_;
int __int_;
unsigned __unsigned_;
long long __long_long_;
unsigned long long __unsigned_long_long_;
# if _LIBCPP_HAS_INT128
__int128_t __i128_;
__uint128_t __u128_;
# endif
float __float_;
double __double_;
long double __long_double_;
const _CharT* __const_char_type_ptr_;
basic_string_view<_CharT> __string_view_;
const void* __ptr_;
__handle __handle_;
};

// These constructors contain the exact storage type used. If adjustments are
// required, these will be done in __create_format_arg.

_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value() noexcept : __monostate_() {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(bool __value) noexcept : __boolean_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(_CharT __value) noexcept : __char_type_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(int __value) noexcept : __int_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(unsigned __value) noexcept : __unsigned_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(long long __value) noexcept : __long_long_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(unsigned long long __value) noexcept
: __unsigned_long_long_(__value) {}
# if _LIBCPP_HAS_INT128
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(__int128_t __value) noexcept : __i128_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(__uint128_t __value) noexcept : __u128_(__value) {}
# endif
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(float __value) noexcept : __float_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(double __value) noexcept : __double_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(long double __value) noexcept : __long_double_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(const _CharT* __value) noexcept : __const_char_type_ptr_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(basic_string_view<_CharT> __value) noexcept
: __string_view_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(const void* __value) noexcept : __ptr_(__value) {}
_LIBCPP_HIDE_FROM_ABI __basic_format_arg_value(__handle&& __value) noexcept : __handle_(std::move(__value)) {}
};

template <class _Context>
class _LIBCPP_NO_SPECIALIZATIONS basic_format_arg {
public:
Expand All @@ -291,42 +357,14 @@ class _LIBCPP_NO_SPECIALIZATIONS basic_format_arg {
// the "variant" in a handle to stay conforming. See __arg_t for more details.
template <class _Visitor>
_LIBCPP_HIDE_FROM_ABI decltype(auto) visit(this basic_format_arg __arg, _Visitor&& __vis) {
switch (__arg.__type_) {
# if _LIBCPP_HAS_INT128
case __format::__arg_t::__i128: {
typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__i128_};
return std::invoke(std::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 std::invoke(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h});
}
# endif
default:
return std::__visit_format_arg(std::forward<_Visitor>(__vis), __arg);
}
return std::__visit_format_arg(std::forward<_Visitor>(__vis), __arg);
}

// 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 _Rp, class _Visitor>
_LIBCPP_HIDE_FROM_ABI _Rp visit(this basic_format_arg __arg, _Visitor&& __vis) {
switch (__arg.__type_) {
# if _LIBCPP_HAS_INT128
case __format::__arg_t::__i128: {
typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__i128_};
return std::invoke_r<_Rp>(std::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 std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h});
}
# endif
default:
return std::__visit_format_arg<_Rp>(std::forward<_Visitor>(__vis), __arg);
}
return std::__visit_format_arg<_Rp>(std::forward<_Visitor>(__vis), __arg);
}

# endif // _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_EXPLICIT_THIS_PARAMETER
Expand Down Expand Up @@ -376,21 +414,7 @@ _LIBCPP_DEPRECATED_IN_CXX26
# endif
_LIBCPP_HIDE_FROM_ABI decltype(auto)
visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg) {
switch (__arg.__type_) {
# if _LIBCPP_HAS_INT128
case __format::__arg_t::__i128: {
typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__i128_};
return std::invoke(std::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 std::invoke(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h});
}
# endif // _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_EXPLICIT_THIS_PARAMETER
default:
return std::__visit_format_arg(std::forward<_Visitor>(__vis), __arg);
}
return std::__visit_format_arg(std::forward<_Visitor>(__vis), __arg);
}

#endif // _LIBCPP_STD_VER >= 20
Expand Down
4 changes: 2 additions & 2 deletions libcxx/include/__format/format_functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ __handle_replacement_field(_Iterator __begin, _Iterator __end, _ParseCtx& __pars
else if (__parse)
__format::__compile_time_visit_format_arg(__parse_ctx, __ctx, __type);
} else
std::__visit_format_arg(
std::__visit_format_arg( // TODO: Use __directly_visit_format_arg?
[&](auto __arg) {
if constexpr (same_as<decltype(__arg), monostate>)
std::__throw_format_error("The argument index value is too large for the number of arguments supplied");
Expand Down Expand Up @@ -468,7 +468,7 @@ template <class _CharT>
if (auto __only_first_arg = __fmt == _LIBCPP_STATICALLY_WIDEN(_CharT, "{}");
__builtin_constant_p(__only_first_arg) && __only_first_arg) {
if (auto __arg = __args.get(0); __builtin_constant_p(__arg.__type_)) {
return std::__visit_format_arg(
return std::__visit_format_arg( // TODO: Use __directly_visit_format_arg?
[]<class _Tp>(_Tp&& __argument) -> optional<basic_string<_CharT>> {
if constexpr (is_same_v<remove_cvref_t<_Tp>, basic_string_view<_CharT>>) {
return basic_string<_CharT>{__argument};
Expand Down
23 changes: 9 additions & 14 deletions libcxx/include/__format/parser_std_format_spec.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,26 +83,21 @@ __parse_arg_id(_Iterator __begin, _Iterator __end, _ParseContext& __ctx) {

template <class _Context>
_LIBCPP_HIDE_FROM_ABI constexpr uint32_t __substitute_arg_id(basic_format_arg<_Context> __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.
// [format.string.std]/10
// [...] The option is valid only if the corresponding formatting argument is of standard signed or unsigned integer
// type. [...]
// This means 128-bit extented integer types are invalid here.
return std::__visit_format_arg(
[](auto __arg) -> uint32_t {
using _Type = decltype(__arg);
if constexpr (same_as<_Type, monostate>)
std::__throw_format_error("The argument index value is too large for the number of arguments supplied");

// [format.string.std]/8
// If { arg-idopt } is used in a width or precision, the value of the
// corresponding formatting argument is used in its place. If the
// corresponding formatting argument is not of standard signed or unsigned
// integer type, or its value is negative for precision or non-positive for
// width, an exception of type format_error is thrown.
// [format.string.std]/10
// If { arg-idopt } is used in a width or precision option, the value of the corresponding formatting argument
// is used as the value of the option. The option is valid only if the corresponding formatting argument is of
// standard signed or unsigned integer type. If its value is negative, an exception of type format_error is
// thrown.
//
// When an integral is used in a format function, it is stored as one of
// the types checked below. Other integral types are promoted. For example,
Expand Down
Loading
Loading