This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

### 3631. `basic_format_arg(T&&)` should use `remove_cvref_t<T>` throughout

Section: 22.14.8.1 [format.arg] Status: New Submitter: Tim Song Opened: 2021-11-03 Last modified: 2022-11-10

Priority: 3

View other active issues in [format.arg].

View all other issues in [format.arg].

View all issues with New status.

Discussion:

P2418R2 changed `basic_format_arg`'s constructor to take a forwarding reference but didn't change 22.14.8.1 [format.arg]/5 which inspects various properties of `T`. Now that the deduced type can be cvref-qualified, they need to be removed before the checks.

[2022-01-29; Reflector poll]

Set priority to 3 after reflector poll.

Two suggestions to just change it to be `T&` because we don't need forwarding references here, and only accepting lvalues prevents forming dangling references.

Previous resolution [SUPERSEDED]:

> This wording is relative to N4901.

> 1. Modify 22.14.8.1 [format.arg] as indicated:

>    >     template<class T> explicit basic_format_arg(T&& v) noexcept;

>    > > -?- Let `TD` be `remove_cvref_t<T>`.

>    > > -4- Constraints: The template specialization

>    > > >     typename Context::template formatter_type<remove_cvref_t<T>TD>

>    > > meets the BasicFormatter requirements (22.14.6.1 [formatter.requirements]). The extent to which an implementation determines that the specialization meets the BasicFormatter requirements is unspecified, except that as a minimum the expression

>    > > >     typename Context::template formatter_type<remove_cvref_t<T>TD>()
>    > > >       .format(declval<T&>(), declval<Context&>())

>    > > shall be well-formed when treated as an unevaluated operand (7.2.3 [expr.context]).

>    > > -5- Effects:

>    > > 1. (5.1) — if `TD` is `bool` or `char_type`, initializes `value` with `v`;

>    > > 2. (5.2) — otherwise, if `TD` is `char` and `char_type` is `wchar_t`, initializes `value` with `static_cast<wchar_t>(v)`;

>    > > 3. (5.3) — otherwise, if `TD` is a signed integer type (6.8.2 [basic.fundamental]) and `sizeof(TD) <= sizeof(int)`, initializes `value` with `static_cast<int>(v)`;

>    > > 4. (5.4) — otherwise, if `TD` is an unsigned integer type and `sizeof(TD) <= sizeof(unsigned int)`, initializes `value` with `static_cast<unsigned int>(v)`;

>    > > 5. (5.5) — otherwise, if `TD` is a signed integer type and `sizeof(TD) <= sizeof(long long int)`, initializes `value` with `static_cast<long long int>(v)`;

>    > > 6. (5.6) — otherwise, if `TD` is an unsigned integer type and `sizeof(TD) <= sizeof(unsigned long long int)`, initializes `value` with `static_cast<unsigned long long int>(v)`;

>    > > 7. (5.7) — otherwise, initializes `value` with `handle(v)`.

[2022-10-19; Jonathan provides improved wording]

During reflector prioritization it was pointed out that forwarding references are unnecessary (arguments are always lvalues), and so using `T&` would be simpler.

In order to resolve the overload ambiguities discussed in 3718 replace all unary constructor overloads with a single constructor that works for any formattable type.

Previous resolution [SUPERSEDED]:

> This wording is relative to N4917.

> 1. Modify 22.14.8.1 [format.arg] as indicated:

>    >     template<class T> explicit basic_format_arg(T&& v) noexcept;

>    > > -4- Constraints: `T` satisfies `formattable<char_type>`. The template specialization

>    > > >     typename Context::template formatter_type<remove_cvref_t<T>>

>    > > meets the BasicFormatter requirements (22.14.6.1 [formatter.requirements]). The extent to which an implementation determines that the specialization meets the BasicFormatter requirements is unspecified, except that as a minimum the expression

>    > > >     typename Context::template formatter_type<remove_cvref_t<T>>()
>    > > >       .format(declval<T&>(), declval<Context&>())

>    > > shall be well-formed when treated as an unevaluated operand (7.2.3 [expr.context]).

>    > > -?- Preconditions: If `decay_t<T>` is `char_type*` or `const char_type*`, `static_cast<const char_type*>(v)` points to a NTCTS (3.37 [defns.ntcts]).

>    > > -5- Effects: Let `TD` be `remove_const_t<T>`.

>    > > 1. (5.1) — if `TD` is `bool` or `char_type`, initializes `value` with `v`;

>    > > 2. (5.2) — otherwise, if `TD` is `char` and `char_type` is `wchar_t`, initializes `value` with `static_cast<wchar_t>(v)`;

>    > > 3. (5.3) — otherwise, if `TD` is a signed integer type (6.8.2 [basic.fundamental]) and `sizeof(TD) <= sizeof(int)`, initializes `value` with `static_cast<int>(v)`;

>    > > 4. (5.4) — otherwise, if `TD` is an unsigned integer type and `sizeof(TD) <= sizeof(unsigned int)`, initializes `value` with `static_cast<unsigned int>(v)`;

>    > > 5. (5.5) — otherwise, if `TD` is a signed integer type and `sizeof(TD) <= sizeof(long long int)`, initializes `value` with `static_cast<long long int>(v)`;

>    > > 6. (5.6) — otherwise, if `TD` is an unsigned integer type and `sizeof(TD) <= sizeof(unsigned long long int)`, initializes `value` with `static_cast<unsigned long long int>(v)`;

>    > > 7. (5.?) — otherwise, if `TD` is a standard floating-point type, initializes `value` with `v`;

>    > > 8. (5.?) — otherwise, if `TD` is a specialization of `basic_string_view` or `basic_string` and `TD::value_type` is `char_type`, initializes `value` with `basic_string_view<char_type>(v.data(), v.size())`;

>    > > 9. (5.?) — otherwise, if `decay_t<TD>` is `char_type*` or `const char_type*`, initializes `value` with `static_cast<const char_type*>(v)`;

>    > > 10. (5.?) — otherwise, if `is_void_v<remove_pointer_t<TD>>` is `true` or `is_null_pointer_v<TD>` is `true`, initializes `value` with `static_cast<const void*>(v)`;

>    > > 11. (5.7) — otherwise, initializes `value` with `handle(v)`.

>    > > -?- [Note: Constructing `basic_format_arg` from a pointer to a member is ill-formed unless the user provides an enabled specialization of `formatter` for that pointer to member type. — end note]

>    >     explicit basic_format_arg(float n) noexcept;
>    >     explicit basic_format_arg(double n) noexcept;
>    >     explicit basic_format_arg(long double n) noexcept;

>    > > -6- Effects: Initializes `value` with `n`.

>    >     explicit basic_format_arg(const char_type* s) noexcept;

>    > > -7- Preconditions: `s` points to a NTCTS (3.37 [defns.ntcts]).

>    > > -8- Effects: Initializes `value` with `s`.

>    >     template<class traits>>
>    >       explicit basic_format_arg(basic_string_view<char_type, traits> s) noexcept;

>    > > -9- Effects: Initializes `value` with `basic_string_view<char_type>(s.data(), s.size())`.

>    >     template<class traits, class Allocator>>
>    >       explicit basic_format_arg(
>    >         const basic_string<char_type, traits, Allocator>& s) noexcept;

>    > > -10- Effects: Initializes `value` with `basic_string_view<char_type>(s.data(), s.size())`.

>    >     explicit basic_format_arg(nullptr_t) noexcept;

>    > > -11- Effects: Initializes `value` with `static_cast<const void*>(nullptr)`.

>    >     template<class T> explicit basic_format_arg(T* p) noexcept;

>    > > -12- Constraints: `is_void_v<T>` is `true`.

>    > > -13- Effects: Initializes `value` with `p`.

>    > > -14- [Note: Constructing `basic_format_arg` from a pointer to a member is ill-formed unless the user provides an enabled specialization of `formatter` for that pointer to member type. — end note]

> 2. Modify 22.14.8.1 [format.arg] p17 and p18 as indicated:

>    >     template<class T> explicit handle(T&& v) noexcept;

>    > > -17- Let

>    > > 1. — (17.1) `TD` be `remove_cvrefconst_t<T>`,

>    > > 2. — (17.2) `const-formattable` be `true` if `typename Context::template formatter_type<TD>().format(declval<const TD&>(), declval<Context&>())` is well-formed, otherwise `false`,

>    > > 3. — (17.3) `TQ` be `const TD` if `const-formattable` is `true` `const TD` satisfies `formattable<char_type>` and `TD` otherwise.

>    > > -18- Mandates: `const-formattable `formattable<const TD, char_type>` || !is_const_v<remove_reference_t<T>` is `true`.

>    > > -19- Effects: Initializes `ptr_` with `addressof(val)` and `format_` with

>    > > >     [](basic_format_parse_context<char_type>& parse_ctx,
>    > > >        Context& format_ctx, const void* ptr) {
>    > > >       typename Context::template formatter_type<TD> f;
>    > > >       parse_ctx.advance_to(f.parse(parse_ctx));
>    > > >       format_ctx.advance_to(f.format(*const_cast<TQ*>(static_cast<const TD*>(ptr)),
>    > > >                                      format_ctx));
>    > > >     }

[2022-11-10; Jonathan provides improved wording]

Proposed resolution:

This wording is relative to N4917.

1. Modify 22.14.6.2 [format.formattable] as indicated:

   -1- Let `fmt-iter-for<charT>` be an unspecified type that models `output_iterator<const charT&>` (25.3.4.10 [iterator.concept.output]).

   >       template<class T, class Context,
   >                class Formatter = typename Context::template formatter_type<remove_const_t<T>>>
   >         concept formattable-with =         // exposition only
   >           semiregular<Formatter> &&
   >             requires (Formatter& f, const Formatter& cf, T&& t, Context fc,
   >                      basic_format_parse_context<typename Context::char_type> pc)
   >            {
   >              { f.parse(pc) } -> same_as<typename decltype(pc)::iterator>;
   >              { cf.format(t, fc) } -> same_as<typename Context::iterator>;
   >            };

   >     template<class T, class charT>
   >       concept formattable =
   >         semiregular<formatter<remove_cvref_t<T>, charT>> &&
   >         requires(formatter<remove_cvref_t<T>, charT> f,
   >                  const formatter<remove_cvref_t<T>, charT> cf,
   >                  T t,
   >                  basic_format_context<fmt-iter-for<charT>, charT> fc,
   >                  basic_format_parse_context<charT> pc) {
   >             { f.parse(pc) } -> same_as<basic_format_parse_context<charT>::iterator>;
   >             { cf.format(t, fc) } -> same_as<fmt-iter-for<charT>>;
   >         };
   >         = formattable-with<remove_reference_t<T>, basic_format_context<fmt-iter-for<charT>>>;

2. Modify 22.14.8.1 [format.arg] as indicated:

   >     template<class T> explicit basic_format_arg(T&& v) noexcept;

   > > -4- Constraints: `T` satisfies `formattable-with<Context>`. The template specialization

   > > >     typename Context::template formatter_type<remove_cvref_t<T>>

   > > meets the BasicFormatter requirements (22.14.6.1 [formatter.requirements]). The extent to which an implementation determines that the specialization meets the BasicFormatter requirements is unspecified, except that as a minimum the expression

   > > >     typename Context::template formatter_type<remove_cvref_t<T>>()
   > > >       .format(declval<T&>(), declval<Context&>())

   > > shall be well-formed when treated as an unevaluated operand (7.2.3 [expr.context]).

   > > -?- Preconditions: If `decay_t<T>` is `char_type*` or `const char_type*`, `static_cast<const char_type*>(v)` points to a NTCTS (3.37 [defns.ntcts]).

   > > -5- Effects: Let `TD` be `remove_const_t<T>`.

   > > 1. (5.1) — if `TD` is `bool` or `char_type`, initializes `value` with `v`;

   > > 2. (5.2) — otherwise, if `TD` is `char` and `char_type` is `wchar_t`, initializes `value` with `static_cast<wchar_t>(v)`;

   > > 3. (5.3) — otherwise, if `TD` is a signed integer type (6.8.2 [basic.fundamental]) and `sizeof(TD) <= sizeof(int)`, initializes `value` with `static_cast<int>(v)`;

   > > 4. (5.4) — otherwise, if `TD` is an unsigned integer type and `sizeof(TD) <= sizeof(unsigned int)`, initializes `value` with `static_cast<unsigned int>(v)`;

   > > 5. (5.5) — otherwise, if `TD` is a signed integer type and `sizeof(TD) <= sizeof(long long int)`, initializes `value` with `static_cast<long long int>(v)`;

   > > 6. (5.6) — otherwise, if `TD` is an unsigned integer type and `sizeof(TD) <= sizeof(unsigned long long int)`, initializes `value` with `static_cast<unsigned long long int>(v)`;

   > > 7. (5.?) — otherwise, if `TD` is a standard floating-point type, initializes `value` with `v`;

   > > 8. (5.?) — otherwise, if `TD` is a specialization of `basic_string_view` or `basic_string` and `TD::value_type` is `char_type`, initializes `value` with `basic_string_view<char_type>(v.data(), v.size())`;

   > > 9. (5.?) — otherwise, if `decay_t<TD>` is `char_type*` or `const char_type*`, initializes `value` with `static_cast<const char_type*>(v)`;

   > > 10. (5.?) — otherwise, if `is_void_v<remove_pointer_t<TD>>` is `true` or `is_null_pointer_v<TD>` is `true`, initializes `value` with `static_cast<const void*>(v)`;

   > > 11. (5.7) — otherwise, initializes `value` with `handle(v)`.

   > > -?- [Note: Constructing `basic_format_arg` from a pointer to a member is ill-formed unless the user provides an enabled specialization of `formatter` for that pointer to member type. — end note]

   >     explicit basic_format_arg(float n) noexcept;
   >     explicit basic_format_arg(double n) noexcept;
   >     explicit basic_format_arg(long double n) noexcept;

   > > -6- Effects: Initializes `value` with `n`.

   >     explicit basic_format_arg(const char_type* s) noexcept;

   > > -7- Preconditions: `s` points to a NTCTS (3.37 [defns.ntcts]).

   > > -8- Effects: Initializes `value` with `s`.

   >     template<class traits>>
   >       explicit basic_format_arg(basic_string_view<char_type, traits> s) noexcept;

   > > -9- Effects: Initializes `value` with `basic_string_view<char_type>(s.data(), s.size())`.

   >     template<class traits, class Allocator>>
   >       explicit basic_format_arg(
   >         const basic_string<char_type, traits, Allocator>& s) noexcept;

   > > -10- Effects: Initializes `value` with `basic_string_view<char_type>(s.data(), s.size())`.

   >     explicit basic_format_arg(nullptr_t) noexcept;

   > > -11- Effects: Initializes `value` with `static_cast<const void*>(nullptr)`.

   >     template<class T> explicit basic_format_arg(T* p) noexcept;

   > > -12- Constraints: `is_void_v<T>` is `true`.

   > > -13- Effects: Initializes `value` with `p`.

   > > -14- [Note: Constructing `basic_format_arg` from a pointer to a member is ill-formed unless the user provides an enabled specialization of `formatter` for that pointer to member type. — end note]

3. Modify 22.14.8.1 [format.arg] p17 and p18 as indicated:

   >     template<class T> explicit handle(T&& v) noexcept;

   > > -17- Let

   > > 1. — (17.1) `TD` be `remove_cvrefconst_t<T>`,

   > > 2. — (17.2) `const-formattable` be `true` if `typename Context::template formatter_type<TD>().format(declval<const TD&>(), declval<Context&>())` is well-formed, otherwise `false`,

   > > 3. — (17.3) `TQ` be `const TD` if `const-formattable` is `true` `const TD` satisfies `formattable-with<Context>` and `TD` otherwise.

   > > -18- Mandates: `const-formattable || !is_const_v<remove_reference_t<T>>` is `true`. `TQ` satisfies `formattable-with<Context>`.

   > > -19- Effects: Initializes `ptr_` with `addressof(val)` and `format_` with

   > > >     [](basic_format_parse_context<char_type>& parse_ctx,
   > > >        Context& format_ctx, const void* ptr) {
   > > >       typename Context::template formatter_type<TD> f;
   > > >       parse_ctx.advance_to(f.parse(parse_ctx));
   > > >       format_ctx.advance_to(f.format(*const_cast<TQ*>(static_cast<const TD*>(ptr)),
   > > >                                      format_ctx));
   > > >     }