Title
P2418R2 broke the overload resolution for std::basic_format_arg
Status
resolved
Section
[format.arg]
Submitter
Jiang An

Created on 2022-06-17.00:00:00 last changed 12 months ago

Messages

Date: 2023-03-22.00:00:00

[ 2023-03-22 Resolved by the adoption of 3631 in Issaquah. Status changed: New → Resolved. ]

Date: 2022-10-15.00:00:00

[ 2022-10-19; Would be resolved by 3631 ]

This wording is relative to N4910.

[Drafting note: The below presented wording adds back the const T& constructor and its specification that were present before P2418R2. Furthermore it adds an additional constraint to the T&& constructor and simplifies the Effects of this constructors to the handle construction case.]

  1. Modify [format.arg] as indicated:

    namespace std {
      template<class Context>
      class basic_format_arg {
      public:
        class handle;
    
      private:
        […]
        template<class T> explicit basic_format_arg(T&& v) noexcept; // exposition only
        template<class T> explicit basic_format_arg(const T& v) noexcept; // exposition only
        […]
      };
    }
    
    […]
    template<class T> explicit basic_format_arg(T&& v) noexcept;
    

    -4- Constraints: 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]), and if this overload were not declared, the overload resolution would find no usable candidate or be ambiguous. [Note ?: This overload has no effect if the overload resolution among other overloads succeeds. — end note].

    -5- Effects:

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

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

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

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

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

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

    7. (5.7) — otherwise, iInitializes value with handle(v).

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

    -?- Constraints: The template specialization

    typename Context::template formatter_type<T>
    

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

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

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

    -?- Effects:

    1. (?.1) — if T is bool or char_type, initializes value with v;

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

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

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

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

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

    7. (?.7) — otherwise, initializes value with handle(v).

Date: 2022-07-15.00:00:00

[ 2022-07-06; Reflector poll ]

Set priority to 2 after reflector poll.

Date: 2022-06-17.00:00:00

While correcting some bugs in MSVC STL, it is found that P2418R2 broke the overload resolution involving non-const lvalue: constructing basic_format_arg from a non-const basic_string lvalue incorrectly selects the T&& overload and uses handle, while the separated basic_string overload should be selected (i.e. the old behavior before P2418R2 did the right thing). Currently MSVC STL is using a workaround that treats basic_string to be as if passed by value during overload resolution.

I think the T&& overload should not interfere the old result of overload resolution, which means that when a type is const-formattable, the newly added non-const mechanism shouldn't be considered.

History
Date User Action Args
2023-03-23 11:42:08adminsetstatus: new -> resolved
2022-10-19 19:43:42adminsetmessages: + msg12873
2022-07-06 16:27:07adminsetmessages: + msg12553
2022-06-17 18:11:58adminsetmessages: + msg12507
2022-06-17 00:00:00admincreate