Title
basic_format_arg(T&&) should use remove_cvref_t<T> throughout
Status
c++23
Section
[format.arg]
Submitter
Tim Song

Created on 2021-11-03.00:00:00 last changed 5 months ago

Messages

Date: 2023-02-13.11:31:32

Proposed resolution:

This wording is relative to N4917.

  1. Modify [format.formattable] as indicated:

    -1- Let fmt-iter-for<charT> be an unspecified type that models output_iterator<const charT&> ([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 [format.arg] as indicated:

    namespace std {
      template<class Context>
      class basic_format_arg {
      public:
        class handle;
    
      private:
        using char_type = typename Context::char_type;                              // exposition only
    
        variant<monostate, bool, char_type,
                int, unsigned int, long long int, unsigned long long int,
                float, double, long double,
                const char_type*, basic_string_view<char_type>,
                const void*, handle> value;                                         // exposition only
    
        template<class T> explicit basic_format_arg(T&& v) noexcept;                // exposition only
        explicit basic_format_arg(float n) noexcept;                                // exposition only
        explicit basic_format_arg(double n) noexcept;                               // exposition only
        explicit basic_format_arg(long double n) noexcept;                          // exposition only
        explicit basic_format_arg(const char_type* s);                              // exposition only
    
        template<class traits>
          explicit basic_format_arg(
            basic_string_view<char_type, traits> s) noexcept;                       // exposition only
    
        template<class traits, class Allocator>
          explicit basic_format_arg(
            const basic_string<char_type, traits, Allocator>& s) noexcept;          // exposition only
    
        explicit basic_format_arg(nullptr_t) noexcept;                              // exposition only
    
        template<class T>
          explicit basic_format_arg(T* p) noexcept;                                 // exposition only
    
    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 ([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 ([expr.context]).

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

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

    1. (5.1) — If 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 ([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 ([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 [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));
    }
    

  4. Modify [format.arg.store] p2 as indicated:

    template<class Context = format_context, class... Args>
      format-arg-store<Context, Args...> make_format_args(Args&&... fmt_args);
    

    -2- Preconditions: The type typename Context::template formatter_type<remove_cvref_t<Ti>> meets the BasicFormatter requirements ([formatter.requirements]) for each Ti in Args.

Date: 2023-02-13.00:00:00

[ 2023-02-13 Approved at February 2023 meeting in Issaquah. Status changed: Immediate → WP. ]

Date: 2023-02-09.02:27:17

[ Issaquah 2023-02-07; LWG ]

Edited proposed resolution to remove extra = in concept definition and capitialize start of (5.1). Move to Immediate for C++23

Date: 2023-01-15.00:00:00

[ 2023-01-11; Jonathan adds the missing edits to the class synopsis ]

Date: 2022-11-15.00:00:00

[ 2022-11-29; Casey expands the issue to also cover make_format_args ]

Date: 2022-11-15.00:00:00

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

Date: 2022-10-15.00:00:00

[ 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 [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 ([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 ([expr.context]).

    -?- Preconditions: If decay_t<T> is char_type* or const char_type*, static_cast<const char_type*>(v) points to a NTCTS ([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 ([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 ([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 [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));
    }
    

Date: 2022-01-15.00:00:00

[ 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 [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 ([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 ([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 ([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).

Date: 2021-11-03.00:00:00

P2418R2 changed basic_format_arg's constructor to take a forwarding reference but didn't change [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.

History
Date User Action Args
2023-11-22 15:47:43adminsetstatus: wp -> c++23
2023-02-13 11:31:32adminsetmessages: + msg13379
2023-02-13 11:31:32adminsetstatus: immediate -> wp
2023-02-09 02:27:17adminsetmessages: + msg13302
2023-02-09 02:27:17adminsetstatus: new -> immediate
2023-01-11 18:22:42adminsetmessages: + msg13202
2022-11-30 09:27:12adminsetmessages: + msg13121
2022-11-10 02:37:23adminsetmessages: + msg12986
2022-10-19 19:43:42adminsetmessages: + msg12872
2022-01-29 22:29:35adminsetmessages: + msg12299
2021-11-06 14:31:32adminsetmessages: + msg12207
2021-11-03 00:00:00admincreate