3340. Formatting functions should throw on argument/format string mismatch in §[format.functions]

Section: 28.5.5 [format.functions] Status: C++20 Submitter: Great Britain Opened: 2019-11-17 Last modified: 2021-02-25

Priority: Not Prioritized

View all other issues in [format.functions].

View all issues with C++20 status.

Discussion:

Addresses GB 229

Formatting functions don't allow throwing on incorrect arguments. std::format is only allowed to throw if fmt is not a format string, but the intention is it also throws for errors during formatting, e.g. there are fewer arguments than required by the format string.

Proposed change:

Allow exceptions even when the format string is valid. Possibly state the Effects: more precisely.

Victor Zverovich:

LEWG approved resolution of this NB comment as an LWG issue.

Previous resolution [SUPERSEDED]:

This wording is relative to N4835.

[Drafting Note: Depending on whether LWG 3336's wording has been accepted when this issue's wording has been accepted, two mutually exclusive options are prepared, depicted below by Option A and Option B, respectively.]

Option A (LWG 3336 has been accepted)

  1. Change 28.5.2.1 [format.string.general] as follows:

    -1- A format string for arguments args is a (possibly empty) sequence of replacement fields, escape sequences, and characters other than { and }. […]

    -2- The arg-id field specifies the index of the argument in args whose value is to be formatted and inserted into the output instead of the replacement field. If there is no argument with the index arg-id in args, the string is not a format string. The optional format-specifier field explicitly specifies a format for the replacement value.

    […]

    -5- The format-spec field contains format specifications that define how the value should be presented. Each type can define its own interpretation of the format-spec field. If format-spec doesn't conform to the format specifications for the argument in args referred to by arg-id, the string is not a format string. […]

  2. Before 28.5.5 [format.functions] insert a new sub-clause as indicated:

    20.20.? Error reporting [format.err.report]

    -?- Formatting functions throw exceptions to report formatting and other errors. They throw format_error if an argument fmt is passed that is not a format string for arguments args and propagate exceptions thrown by formatter specializations and iterator operations. Failure to allocate storage is reported by throwing an exception as described in 16.4.6.13 [res.on.exception.handling].

  3. Modify 28.5.5 [format.functions] as indicated:

    string vformat(string_view fmt, format_args args);
    wstring vformat(wstring_view fmt, wformat_args args);
    string vformat(const locale& loc, string_view fmt, format_args args);
    wstring vformat(const locale& loc, wstring_view fmt, wformat_args args);
    

    -6- […]

    -7- Throws: format_error if fmt is not a format stringAs specified in 28.5.3 [format.err.report].

    […]
    template<class Out>
      Out vformat_to(Out out, string_view fmt, format_args_t<Out, char> args);
    template<class Out>
      Out vformat_to(Out out, wstring_view fmt, format_args_t<Out, wchar_t> args);
    template<class Out>
      Out vformat_to(Out out, const locale& loc, string_view fmt,
                     format_args_t<Out, char> args);
    template<class Out>
      Out vformat_to(Out out, const locale& loc, wstring_view fmt,
                     format_args_t<Out, wchar_t> args);
    

    […]

    -15- Throws: format_error if fmt is not a format stringAs specified in 28.5.3 [format.err.report].

    […]
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          string_view fmt, const Args&... args);
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          wstring_view fmt, const Args&... args);
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          const locale& loc, string_view fmt,
                                          const Args&... args);
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          const locale& loc, wstring_view fmt,
                                          const Args&... args);
    

    […]

    -21- Throws: format_error if fmt is not a format stringAs specified in 28.5.3 [format.err.report].

    […]
    template<class... Args>
      size_t formatted_size(string_view fmt, const Args&... args);
    template<class... Args>
      size_t formatted_size(wstring_view fmt, const Args&... args);
    template<class... Args>
      size_t formatted_size(const locale& loc, string_view fmt, const Args&... args);
    template<class... Args>
      size_t formatted_size(const locale& loc, wstring_view fmt, const Args&... args);
    

    […]

    -25- Throws: format_error if fmt is not a format stringAs specified in 28.5.3 [format.err.report].

Option B (LWG 3336 has not been accepted)

  1. Change 28.5.2.1 [format.string.general] as follows:

    -1- A format string for arguments args is a (possibly empty) sequence of replacement fields, escape sequences, and characters other than { and }. […]

    -2- The arg-id field specifies the index of the argument in args whose value is to be formatted and inserted into the output instead of the replacement field. If there is no argument with the index arg-id in args, the string is not a format string. The optional format-specifier field explicitly specifies a format for the replacement value.

    […]

    -5- The format-spec field contains format specifications that define how the value should be presented. Each type can define its own interpretation of the format-spec field. If format-spec doesn't conform to the format specifications for the argument in args referred to by arg-id, the string is not a format string. […]

  2. Modify 28.5.5 [format.functions] as indicated:

    string vformat(string_view fmt, format_args args);
    wstring vformat(wstring_view fmt, wformat_args args);
    string vformat(const locale& loc, string_view fmt, format_args args);
    wstring vformat(const locale& loc, wstring_view fmt, wformat_args args);
    

    -6- […]

    -7- Throws: format_error if fmt is not a format string for args.

    […]
    template<class Out>
      Out vformat_to(Out out, string_view fmt, format_args_t<Out, char> args);
    template<class Out>
      Out vformat_to(Out out, wstring_view fmt, format_args_t<Out, wchar_t> args);
    template<class Out>
      Out vformat_to(Out out, const locale& loc, string_view fmt,
                     format_args_t<Out, char> args);
    template<class Out>
      Out vformat_to(Out out, const locale& loc, wstring_view fmt,
                     format_args_t<Out, wchar_t> args);
    

    […]

    -15- Throws: format_error if fmt is not a format string for args.

    […]
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          string_view fmt, const Args&... args);
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          wstring_view fmt, const Args&... args);
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          const locale& loc, string_view fmt,
                                          const Args&... args);
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          const locale& loc, wstring_view fmt,
                                          const Args&... args);
    

    […]

    -21- Throws: format_error if fmt is not a format string for args.

    […]
    template<class... Args>
      size_t formatted_size(string_view fmt, const Args&... args);
    template<class... Args>
      size_t formatted_size(wstring_view fmt, const Args&... args);
    template<class... Args>
      size_t formatted_size(const locale& loc, string_view fmt, const Args&... args);
    template<class... Args>
      size_t formatted_size(const locale& loc, wstring_view fmt, const Args&... args);
    

    […]

    -25- Throws: format_error if fmt is not a format string for args.

[2020-02-12, Prague; LWG discussion]

Option A is the only one we look at to resolve LWG 3336 as well. During the discussions some wording refinements have been suggested that are integrated below.

Proposed resolution:

This wording is relative to N4849.

  1. Change 28.5.2.1 [format.string.general] as follows:

    -1- A format string for arguments args is a (possibly empty) sequence of replacement fields, escape sequences, and characters other than { and }. […]

    -2- The arg-id field specifies the index of the argument in args whose value is to be formatted and inserted into the output instead of the replacement field. If there is no argument with the index arg-id in args, the string is not a format string for args. The optional format-specifier field explicitly specifies a format for the replacement value.

    […]

    -5- The format-spec field contains format specifications that define how the value should be presented. Each type can define its own interpretation of the format-spec field. If format-spec does not conform to the format specifications for the argument type referred to by arg-id, the string is not a format string for args. […]

  2. Before 28.5.5 [format.functions] insert a new sub-clause as indicated:

    20.20.? Error reporting [format.err.report]

    -?- Formatting functions throw format_error if an argument fmt is passed that is not a format string for args. They propagate exceptions thrown by operations of formatter specializations and iterators. Failure to allocate storage is reported by throwing an exception as described in 16.4.6.13 [res.on.exception.handling].

  3. Modify 28.5.5 [format.functions] as indicated:

    string vformat(string_view fmt, format_args args);
    wstring vformat(wstring_view fmt, wformat_args args);
    string vformat(const locale& loc, string_view fmt, format_args args);
    wstring vformat(const locale& loc, wstring_view fmt, wformat_args args);
    

    -6- […]

    -7- Throws: format_error if fmt is not a format stringAs specified in 28.5.3 [format.err.report].

    […]
    template<class Out>
      Out vformat_to(Out out, string_view fmt, format_args_t<Out, char> args);
    template<class Out>
      Out vformat_to(Out out, wstring_view fmt, format_args_t<Out, wchar_t> args);
    template<class Out>
      Out vformat_to(Out out, const locale& loc, string_view fmt,
                     format_args_t<Out, char> args);
    template<class Out>
      Out vformat_to(Out out, const locale& loc, wstring_view fmt,
                     format_args_t<Out, wchar_t> args);
    

    […]

    -15- Throws: format_error if fmt is not a format stringAs specified in 28.5.3 [format.err.report].

    […]
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          string_view fmt, const Args&... args);
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          wstring_view fmt, const Args&... args);
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          const locale& loc, string_view fmt,
                                          const Args&... args);
    template<class Out, class... Args>
      format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
                                          const locale& loc, wstring_view fmt,
                                          const Args&... args);
    

    […]

    -21- Throws: format_error if fmt is not a format stringAs specified in 28.5.3 [format.err.report].

    […]
    template<class... Args>
      size_t formatted_size(string_view fmt, const Args&... args);
    template<class... Args>
      size_t formatted_size(wstring_view fmt, const Args&... args);
    template<class... Args>
      size_t formatted_size(const locale& loc, string_view fmt, const Args&... args);
    template<class... Args>
      size_t formatted_size(const locale& loc, wstring_view fmt, const Args&... args);
    

    […]

    -25- Throws: format_error if fmt is not a format stringAs specified in 28.5.3 [format.err.report].